@cloudcare/browser-core 3.1.23 → 3.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 (105) hide show
  1. package/cjs/browser/pageExitObservable.js +1 -1
  2. package/cjs/browser/pageExitObservable.js.map +1 -1
  3. package/cjs/browser/runOnReadyState.js +5 -1
  4. package/cjs/browser/runOnReadyState.js.map +1 -1
  5. package/cjs/console/consoleObservable.js +17 -9
  6. package/cjs/console/consoleObservable.js.map +1 -1
  7. package/cjs/dataMap.js +11 -2
  8. package/cjs/dataMap.js.map +1 -1
  9. package/cjs/helper/boundedBuffer.js +21 -17
  10. package/cjs/helper/boundedBuffer.js.map +1 -1
  11. package/cjs/helper/connectivity.js +16 -0
  12. package/cjs/helper/connectivity.js.map +1 -0
  13. package/cjs/helper/display.js +15 -12
  14. package/cjs/helper/display.js.map +1 -1
  15. package/cjs/helper/displayAlreadyInitializedError.js +13 -0
  16. package/cjs/helper/displayAlreadyInitializedError.js.map +1 -0
  17. package/cjs/helper/encoder.js +43 -0
  18. package/cjs/helper/encoder.js.map +1 -0
  19. package/cjs/helper/lifeCycle.js +0 -1
  20. package/cjs/helper/lifeCycle.js.map +1 -1
  21. package/cjs/helper/monitor.js +6 -6
  22. package/cjs/helper/monitor.js.map +1 -1
  23. package/cjs/helper/serialisation/jsonStringify.js.map +1 -1
  24. package/cjs/index.js +33 -0
  25. package/cjs/index.js.map +1 -1
  26. package/cjs/report/reportObservable.js +26 -20
  27. package/cjs/report/reportObservable.js.map +1 -1
  28. package/cjs/telemetry/telemetry.js +60 -31
  29. package/cjs/telemetry/telemetry.js.map +1 -1
  30. package/cjs/telemetry/types.js +2 -1
  31. package/cjs/telemetry/types.js.map +1 -1
  32. package/cjs/transport/batch.js +129 -79
  33. package/cjs/transport/batch.js.map +1 -1
  34. package/cjs/transport/flushController.js +18 -5
  35. package/cjs/transport/flushController.js.map +1 -1
  36. package/cjs/transport/httpRequest.js +35 -22
  37. package/cjs/transport/httpRequest.js.map +1 -1
  38. package/cjs/transport/index.js +4 -4
  39. package/cjs/transport/index.js.map +1 -1
  40. package/cjs/transport/startBatchWithReplica.js +26 -10
  41. package/cjs/transport/startBatchWithReplica.js.map +1 -1
  42. package/esm/browser/pageExitObservable.js +1 -1
  43. package/esm/browser/pageExitObservable.js.map +1 -1
  44. package/esm/browser/runOnReadyState.js +5 -1
  45. package/esm/browser/runOnReadyState.js.map +1 -1
  46. package/esm/console/consoleObservable.js +18 -10
  47. package/esm/console/consoleObservable.js.map +1 -1
  48. package/esm/dataMap.js +11 -2
  49. package/esm/dataMap.js.map +1 -1
  50. package/esm/helper/boundedBuffer.js +21 -16
  51. package/esm/helper/boundedBuffer.js.map +1 -1
  52. package/esm/helper/connectivity.js +10 -0
  53. package/esm/helper/connectivity.js.map +1 -0
  54. package/esm/helper/display.js +12 -11
  55. package/esm/helper/display.js.map +1 -1
  56. package/esm/helper/displayAlreadyInitializedError.js +7 -0
  57. package/esm/helper/displayAlreadyInitializedError.js.map +1 -0
  58. package/esm/helper/encoder.js +37 -0
  59. package/esm/helper/encoder.js.map +1 -0
  60. package/esm/helper/lifeCycle.js +0 -1
  61. package/esm/helper/lifeCycle.js.map +1 -1
  62. package/esm/helper/monitor.js +6 -6
  63. package/esm/helper/monitor.js.map +1 -1
  64. package/esm/helper/serialisation/jsonStringify.js.map +1 -1
  65. package/esm/index.js +3 -0
  66. package/esm/index.js.map +1 -1
  67. package/esm/report/reportObservable.js +27 -21
  68. package/esm/report/reportObservable.js.map +1 -1
  69. package/esm/telemetry/telemetry.js +57 -31
  70. package/esm/telemetry/telemetry.js.map +1 -1
  71. package/esm/telemetry/types.js +2 -1
  72. package/esm/telemetry/types.js.map +1 -1
  73. package/esm/transport/batch.js +127 -77
  74. package/esm/transport/batch.js.map +1 -1
  75. package/esm/transport/flushController.js +18 -5
  76. package/esm/transport/flushController.js.map +1 -1
  77. package/esm/transport/httpRequest.js +35 -22
  78. package/esm/transport/httpRequest.js.map +1 -1
  79. package/esm/transport/index.js +1 -1
  80. package/esm/transport/index.js.map +1 -1
  81. package/esm/transport/startBatchWithReplica.js +27 -11
  82. package/esm/transport/startBatchWithReplica.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/browser/pageExitObservable.js +2 -2
  85. package/src/browser/runOnReadyState.js +3 -1
  86. package/src/console/consoleObservable.js +22 -14
  87. package/src/dataMap.js +11 -2
  88. package/src/helper/boundedBuffer.js +21 -14
  89. package/src/helper/connectivity.js +11 -0
  90. package/src/helper/display.js +16 -14
  91. package/src/helper/displayAlreadyInitializedError.js +7 -0
  92. package/src/helper/encoder.js +43 -0
  93. package/src/helper/lifeCycle.js +0 -1
  94. package/src/helper/monitor.js +6 -6
  95. package/src/helper/serialisation/jsonStringify.js +1 -1
  96. package/src/index.js +3 -0
  97. package/src/report/reportObservable.js +43 -27
  98. package/src/telemetry/telemetry.js +60 -37
  99. package/src/telemetry/types.js +2 -1
  100. package/src/transport/batch.js +154 -89
  101. package/src/transport/flushController.js +19 -6
  102. package/src/transport/httpRequest.js +34 -35
  103. package/src/transport/index.js +1 -1
  104. package/src/transport/startBatchWithReplica.js +25 -14
  105. package/types/index.d.ts +12 -0
@@ -3,8 +3,10 @@ import {
3
3
  toStackTraceString,
4
4
  NO_ERROR_STACK_PRESENT_MESSAGE
5
5
  } from '../helper/errorTools'
6
+ import { createBoundedBuffer } from '../helper/boundedBuffer'
6
7
  import { computeStackTrace } from '../tracekit'
7
8
  import { Observable } from '../helper/observable'
9
+ import { getConnectivity } from '../helper/connectivity'
8
10
  import {
9
11
  displayIfDebugEnabled,
10
12
  startMonitorErrorCollection
@@ -31,35 +33,37 @@ export var TelemetryService = {
31
33
  RUM: 'browser-rum-sdk'
32
34
  }
33
35
 
34
- var telemetryConfiguration = {
35
- maxEventsPerPage: 0,
36
- sentEventCount: 0,
37
- telemetryEnabled: false
36
+ // eslint-disable-next-line local-rules/disallow-side-effects
37
+ var preStartTelemetryBuffer = createBoundedBuffer()
38
+ var onRawTelemetryEventCollected = function (event) {
39
+ preStartTelemetryBuffer.add(function () {
40
+ onRawTelemetryEventCollected(event)
41
+ })
38
42
  }
39
43
 
40
- var onRawTelemetryEventCollected
41
-
42
44
  export function startTelemetry(telemetryService, configuration) {
43
45
  let contextProvider
44
46
  var observable = new Observable()
45
- telemetryConfiguration.telemetryEnabled =
47
+ const alreadySentEvents = new Set()
48
+ const telemetryEnabled =
46
49
  configuration.telemetryEnabled &&
47
50
  performDraw(configuration.telemetrySampleRate)
48
-
51
+ const runtimeEnvInfo = getRuntimeEnvInfo()
49
52
  onRawTelemetryEventCollected = function (rawEvent) {
50
- if (telemetryConfiguration.telemetryEnabled) {
51
- var event = toTelemetryEvent(telemetryService, rawEvent)
53
+ const stringifiedEvent = jsonStringify(rawEvent)
54
+ if (
55
+ telemetryEnabled &&
56
+ alreadySentEvents.size < configuration.maxTelemetryEventsPerPage &&
57
+ !alreadySentEvents.has(stringifiedEvent)
58
+ ) {
59
+ var event = toTelemetryEvent(telemetryService, rawEvent, runtimeEnvInfo)
52
60
  observable.notify(event)
61
+ alreadySentEvents.add(stringifiedEvent)
53
62
  }
54
63
  }
55
64
  startMonitorErrorCollection(addTelemetryError)
56
65
 
57
- assign(telemetryConfiguration, {
58
- maxEventsPerPage: configuration.maxTelemetryEventsPerPage,
59
- sentEventCount: 0
60
- })
61
-
62
- function toTelemetryEvent(telemetryService, event) {
66
+ function toTelemetryEvent(telemetryService, event, runtimeEnvInfo) {
63
67
  return extend2Lev(
64
68
  {
65
69
  type: 'telemetry',
@@ -67,7 +71,10 @@ export function startTelemetry(telemetryService, configuration) {
67
71
  service: telemetryService,
68
72
  version: __BUILD_ENV__SDK_VERSION__,
69
73
  source: 'browser',
70
- telemetry: event // https://github.com/microsoft/TypeScript/issues/48457
74
+ telemetry: extend2Lev(event, {
75
+ runtime_env: runtimeEnvInfo,
76
+ connectivity: getConnectivity()
77
+ })
71
78
  },
72
79
  contextProvider !== undefined ? contextProvider() : {}
73
80
  )
@@ -78,17 +85,39 @@ export function startTelemetry(telemetryService, configuration) {
78
85
  contextProvider = provider
79
86
  },
80
87
  observable: observable,
81
- enabled: telemetryConfiguration.telemetryEnabled
88
+ enabled: telemetryEnabled
82
89
  }
83
90
  }
91
+ function getRuntimeEnvInfo() {
92
+ return {
93
+ is_local_file: window.location.protocol === 'file:',
94
+ is_worker: 'WorkerGlobalScope' in self
95
+ }
96
+ }
97
+ export function startFakeTelemetry() {
98
+ const events = []
99
+
100
+ onRawTelemetryEventCollected = function (event) {
101
+ events.push(event)
102
+ }
84
103
 
104
+ return events
105
+ }
106
+ export function drainPreStartTelemetry() {
107
+ preStartTelemetryBuffer.drain()
108
+ }
85
109
  export function resetTelemetry() {
86
- onRawTelemetryEventCollected = undefined
110
+ preStartTelemetryBuffer = createBoundedBuffer()
111
+ onRawTelemetryEventCollected = function (event) {
112
+ preStartTelemetryBuffer.add(function () {
113
+ onRawTelemetryEventCollected(event)
114
+ })
115
+ }
87
116
  }
88
117
 
89
118
  export function addTelemetryDebug(message, context) {
90
- displayIfDebugEnabled(ConsoleApiName.debug, message, context)
91
- addTelemetry(
119
+ displayIfDebugEnabled(message, context)
120
+ onRawTelemetryEventCollected(
92
121
  assign(
93
122
  {
94
123
  type: TelemetryType.log,
@@ -101,7 +130,7 @@ export function addTelemetryDebug(message, context) {
101
130
  }
102
131
 
103
132
  export function addTelemetryError(e, context) {
104
- addTelemetry(
133
+ onRawTelemetryEventCollected(
105
134
  assign(
106
135
  {
107
136
  type: TelemetryType.log,
@@ -114,23 +143,17 @@ export function addTelemetryError(e, context) {
114
143
  }
115
144
 
116
145
  export function addTelemetryConfiguration(configuration) {
117
- if (telemetryConfiguration.telemetryEnabled) {
118
- addTelemetry({
119
- type: TelemetryType.configuration,
120
- configuration: configuration
121
- })
122
- }
146
+ onRawTelemetryEventCollected({
147
+ type: TelemetryType.configuration,
148
+ configuration: configuration
149
+ })
123
150
  }
124
151
 
125
- function addTelemetry(event) {
126
- if (
127
- onRawTelemetryEventCollected &&
128
- telemetryConfiguration.sentEventCount <
129
- telemetryConfiguration.maxEventsPerPage
130
- ) {
131
- telemetryConfiguration.sentEventCount += 1
132
- onRawTelemetryEventCollected(event)
133
- }
152
+ export function addTelemetryUsage(usage) {
153
+ onRawTelemetryEventCollected({
154
+ type: TelemetryType.usage,
155
+ usage: usage
156
+ })
134
157
  }
135
158
 
136
159
  export function formatError(e) {
@@ -1,6 +1,7 @@
1
1
  export var TelemetryType = {
2
2
  log: 'log',
3
- configuration: 'configuration'
3
+ configuration: 'configuration',
4
+ usage: 'usage'
4
5
  }
5
6
 
6
7
  export var TelemetryStatusType = {
@@ -22,7 +22,6 @@ import { isPageExitReason } from '../browser/pageExitObservable'
22
22
  import { jsonStringify } from '../helper/serialisation/jsonStringify'
23
23
  // https://en.wikipedia.org/wiki/UTF-8
24
24
  // eslint-disable-next-line no-control-regex
25
- var HAS_MULTI_BYTES_CHARACTERS = /[^\u0000-\u007F]/
26
25
  var CUSTOM_KEYS = 'custom_keys'
27
26
  export var processedMessageByDataMap = function (message) {
28
27
  if (!message || !message.type)
@@ -128,109 +127,175 @@ export var processedMessageByDataMap = function (message) {
128
127
  rowData: hasFileds ? rowData : undefined
129
128
  }
130
129
  }
131
- var batch = function (
132
- request,
133
- flushController,
134
- messageBytesLimit,
135
- sendContentTypeByJson
136
- ) {
137
- this.pushOnlyBuffer = []
138
- this.upsertBuffer = {}
139
- this.request = request
140
- this.flushController = flushController
141
- this.messageBytesLimit = messageBytesLimit
142
- this.sendContentTypeByJson = sendContentTypeByJson
143
- var _this = this
144
- this.flushController.flushObservable.subscribe(function (event) {
145
- _this.flush(event)
130
+
131
+ export function createBatch(options) {
132
+ var encoder = options.encoder
133
+ var request = options.request
134
+ var messageBytesLimit = options.messageBytesLimit
135
+ var sendContentTypeByJson = options.sendContentTypeByJson
136
+ var flushController = options.flushController
137
+ var upsertBuffer = {}
138
+ var flushSubscription = flushController.flushObservable.subscribe(function (
139
+ event
140
+ ) {
141
+ flush(event)
146
142
  })
147
- }
148
- batch.prototype.add = function (message) {
149
- this.addOrUpdate(message)
150
- }
151
- batch.prototype.upsert = function (message, key) {
152
- this.addOrUpdate(message, key)
153
- }
154
- batch.prototype.flush = function (event) {
155
- var messages = this.pushOnlyBuffer.concat(values(this.upsertBuffer))
156
- this.pushOnlyBuffer = []
157
- this.upsertBuffer = {}
158
- if (messages.length > 0) {
159
- var payloadData = ''
160
- if (this.sendContentTypeByJson) {
161
- payloadData = '[' + messages.join(',') + ']'
143
+ function getMessageText(messages, isEmpty = false) {
144
+ if (sendContentTypeByJson) {
145
+ if (isEmpty) {
146
+ return '[' + messages.join(',')
147
+ } else {
148
+ return ',' + messages.join(',')
149
+ }
162
150
  } else {
163
- payloadData = messages.join('\n')
164
- }
165
- var payload = {
166
- data: payloadData,
167
- bytesCount: event.bytesCount,
168
- flushReason: event.reason
151
+ if (isEmpty) {
152
+ return messages.join('\n')
153
+ } else {
154
+ return '\n' + messages.join('\n')
155
+ }
169
156
  }
170
- if (isPageExitReason(event.reason)) {
171
- this.request.sendOnExit(payload)
157
+ }
158
+ function push(serializedMessage, estimatedMessageBytesCount, key) {
159
+ flushController.notifyBeforeAddMessage(estimatedMessageBytesCount)
160
+
161
+ if (key !== undefined) {
162
+ upsertBuffer[key] = serializedMessage
163
+ flushController.notifyAfterAddMessage()
172
164
  } else {
173
- this.request.send(payload)
165
+ encoder.write(
166
+ getMessageText([serializedMessage], encoder.isEmpty()),
167
+ function (realMessageBytesCount) {
168
+ flushController.notifyAfterAddMessage(
169
+ realMessageBytesCount - estimatedMessageBytesCount
170
+ )
171
+ }
172
+ )
174
173
  }
175
174
  }
176
- }
177
175
 
178
- batch.prototype.addOrUpdate = function (message, key) {
179
- var _process = this.process(message)
180
- var processedMessage = _process.processedMessage
181
- var messageBytesCount = _process.messageBytesCount
182
- if (messageBytesCount >= this.messageBytesLimit) {
183
- display.warn(
184
- 'Discarded a message whose size was bigger than the maximum allowed size ' +
185
- this.messageBytesLimit +
186
- 'KB.'
187
- )
188
- return
176
+ function hasMessageFor(key) {
177
+ return key !== undefined && upsertBuffer[key] !== undefined
189
178
  }
190
- if (this.hasMessageFor(key)) {
191
- this.remove(key)
179
+
180
+ function remove(key) {
181
+ var removedMessage = upsertBuffer[key]
182
+ delete upsertBuffer[key]
183
+ var messageBytesCount = encoder.estimateEncodedBytesCount(removedMessage)
184
+ flushController.notifyAfterRemoveMessage(messageBytesCount)
192
185
  }
193
- this.push(processedMessage, messageBytesCount, key)
194
- }
195
- batch.prototype.process = function (message) {
196
- var processedMessage = ''
197
- if (this.sendContentTypeByJson) {
198
- processedMessage = jsonStringify(processedMessageByDataMap(message).rowData)
199
- } else {
200
- processedMessage = processedMessageByDataMap(message).rowStr
186
+ function process(message) {
187
+ var processedMessage = ''
188
+ if (sendContentTypeByJson) {
189
+ processedMessage = jsonStringify(
190
+ processedMessageByDataMap(message).rowData
191
+ )
192
+ } else {
193
+ processedMessage = processedMessageByDataMap(message).rowStr
194
+ }
195
+ return processedMessage
196
+ }
197
+ function addOrUpdate(message, key) {
198
+ const serializedMessage = process(message)
199
+
200
+ const estimatedMessageBytesCount =
201
+ encoder.estimateEncodedBytesCount(serializedMessage)
202
+
203
+ if (estimatedMessageBytesCount >= messageBytesLimit) {
204
+ display.warn(
205
+ `Discarded a message whose size was bigger than the maximum allowed size ${messageBytesLimit}KB.`
206
+ )
207
+ return
208
+ }
209
+
210
+ if (hasMessageFor(key)) {
211
+ remove(key)
212
+ }
213
+
214
+ push(serializedMessage, estimatedMessageBytesCount, key)
215
+ }
216
+
217
+ function flush(event) {
218
+ var upsertMessages = values(upsertBuffer).join(
219
+ sendContentTypeByJson ? ',' : '\n'
220
+ )
221
+ upsertBuffer = {}
222
+
223
+ var isPageExit = isPageExitReason(event.reason)
224
+ var send = isPageExit ? request.sendOnExit : request.send
225
+
226
+ if (
227
+ isPageExit &&
228
+ // Note: checking that the encoder is async is not strictly needed, but it's an optimization:
229
+ // if the encoder is async we need to send two requests in some cases (one for encoded data
230
+ // and the other for non-encoded data). But if it's not async, we don't have to worry about
231
+ // it and always send a single request.
232
+ encoder.isAsync
233
+ ) {
234
+ // 咱不支持json 模式
235
+ var encoderResult = encoder.finishSync()
236
+
237
+ // Send encoded messages
238
+ if (encoderResult.outputBytesCount) {
239
+ send(formatPayloadFromEncoder(encoderResult, sendContentTypeByJson))
240
+ }
241
+
242
+ // Send messages that are not yet encoded at this point
243
+ var pendingMessages = [encoderResult.pendingData, upsertMessages]
244
+ .filter(Boolean)
245
+ .join('\n')
246
+
247
+ if (pendingMessages) {
248
+ send({
249
+ data: pendingMessages,
250
+ bytesCount: computeBytesCount(pendingMessages)
251
+ })
252
+ }
253
+ } else {
254
+ if (upsertMessages) {
255
+ var text = getMessageText([upsertMessages], encoder.isEmpty())
256
+ if (sendContentTypeByJson) {
257
+ text += ']'
258
+ }
259
+ encoder.write(text)
260
+ } else {
261
+ if (sendContentTypeByJson) {
262
+ encoder.write(']')
263
+ }
264
+ }
265
+ encoder.finish(function (encoderResult) {
266
+ send(formatPayloadFromEncoder(encoderResult))
267
+ })
268
+ }
201
269
  }
202
- var messageBytesCount = computeBytesCount(processedMessage)
270
+
203
271
  return {
204
- processedMessage: processedMessage,
205
- messageBytesCount: messageBytesCount
272
+ flushController: flushController,
273
+ add: addOrUpdate,
274
+ upsert: addOrUpdate,
275
+ stop: flushSubscription.unsubscribe
206
276
  }
207
277
  }
208
278
 
209
- batch.prototype.push = function (processedMessage, messageBytesCount, key) {
210
- var separatorBytesCount = this.flushController.getMessagesCount() > 0 ? 1 : 0
211
- this.flushController.notifyBeforeAddMessage(
212
- messageBytesCount + separatorBytesCount
213
- )
214
- if (key !== undefined) {
215
- this.upsertBuffer[key] = processedMessage
279
+ function formatPayloadFromEncoder(encoderResult, sendContentTypeByJson) {
280
+ var data
281
+ if (typeof encoderResult.output === 'string') {
282
+ data = encoderResult.output
216
283
  } else {
217
- this.pushOnlyBuffer.push(processedMessage)
284
+ data = new Blob([encoderResult.output], {
285
+ // This will set the 'Content-Type: text/plain' header. Reasoning:
286
+ // * The intake rejects the request if there is no content type.
287
+ // * The browser will issue CORS preflight requests if we set it to 'application/json', which
288
+ // could induce higher intake load (and maybe has other impacts).
289
+ // * Also it's not quite JSON, since we are concatenating multiple JSON objects separated by
290
+ // new lines.
291
+ type: 'text/plain'
292
+ })
218
293
  }
219
- this.flushController.notifyAfterAddMessage()
220
- }
221
-
222
- batch.prototype.remove = function (key) {
223
- var removedMessage = this.upsertBuffer[key]
224
- delete this.upsertBuffer[key]
225
- var messageBytesCount = computeBytesCount(removedMessage)
226
- // If there are other messages, a '\n' will be added at serialization
227
- var separatorBytesCount = this.flushController.getMessagesCount() > 1 ? 1 : 0
228
- this.flushController.notifyAfterRemoveMessage(
229
- messageBytesCount + separatorBytesCount
230
- )
231
- }
232
294
 
233
- batch.prototype.hasMessageFor = function (key) {
234
- return key !== undefined && this.upsertBuffer[key] !== undefined
295
+ return {
296
+ data: data,
297
+ type: sendContentTypeByJson ? 'application/json;UTF-8' : undefined,
298
+ bytesCount: encoderResult.outputBytesCount,
299
+ encoding: encoderResult.encoding
300
+ }
235
301
  }
236
- export var Batch = batch
@@ -15,14 +15,18 @@ export function createFlushController({
15
15
  pageExitObservable,
16
16
  sessionExpireObservable
17
17
  }) {
18
- var flushObservable = new Observable()
19
-
20
18
  pageExitObservable.subscribe(function (event) {
21
19
  return flush(event.reason)
22
20
  })
23
21
  sessionExpireObservable.subscribe(function () {
24
22
  return flush('session_expire')
25
23
  })
24
+ var flushObservable = new Observable(function () {
25
+ return function () {
26
+ pageExitSubscription.unsubscribe()
27
+ sessionExpireSubscription.unsubscribe()
28
+ }
29
+ })
26
30
 
27
31
  var currentBytesCount = 0
28
32
  var currentMessagesCount = 0
@@ -72,15 +76,15 @@ export function createFlushController({
72
76
  * This function needs to be called synchronously, right before adding the message, so no flush
73
77
  * event can happen after `notifyBeforeAddMessage` and before adding the message.
74
78
  */
75
- notifyBeforeAddMessage: function (messageBytesCount) {
76
- if (currentBytesCount + messageBytesCount >= bytesLimit) {
79
+ notifyBeforeAddMessage: function (estimatedMessageBytesCount) {
80
+ if (currentBytesCount + estimatedMessageBytesCount >= bytesLimit) {
77
81
  flush('bytes_limit')
78
82
  }
79
83
  // Consider the message to be added now rather than in `notifyAfterAddMessage`, because if no
80
84
  // message was added yet and `notifyAfterAddMessage` is called asynchronously, we still want
81
85
  // to notify when a flush is needed (for example on page exit).
82
86
  currentMessagesCount += 1
83
- currentBytesCount += messageBytesCount
87
+ currentBytesCount += estimatedMessageBytesCount
84
88
  scheduleDurationLimitTimeout()
85
89
  },
86
90
 
@@ -90,7 +94,12 @@ export function createFlushController({
90
94
  * This function can be called asynchronously after the message was added, but in this case it
91
95
  * should not be called if a flush event occurred in between.
92
96
  */
93
- notifyAfterAddMessage: function () {
97
+ notifyAfterAddMessage: function (messageBytesCountDiff) {
98
+ if (messageBytesCountDiff === undefined) {
99
+ messageBytesCountDiff = 0
100
+ }
101
+ currentBytesCount += messageBytesCountDiff
102
+
94
103
  if (currentMessagesCount >= messagesLimit) {
95
104
  flush('messages_limit')
96
105
  } else if (currentBytesCount >= bytesLimit) {
@@ -103,6 +112,10 @@ export function createFlushController({
103
112
  *
104
113
  * This function needs to be called synchronously, right after removing the message, so no flush
105
114
  * event can happen after removing the message and before `notifyAfterRemoveMessage`.
115
+ *
116
+ * @param messageBytesCount: the message bytes count that was added to the pool. Should
117
+ * correspond to the sum of bytes counts passed to `notifyBeforeAddMessage` and
118
+ * `notifyAfterAddMessage`.
106
119
  */
107
120
  notifyAfterRemoveMessage: function (messageBytesCount) {
108
121
  currentBytesCount -= messageBytesCount
@@ -1,6 +1,7 @@
1
1
  import { newRetryState, sendWithRetryStrategy } from './sendWithRetryStrategy'
2
2
  import { addEventListener } from '../browser/addEventListener'
3
3
  import { monitor } from '../helper/monitor'
4
+ import { addTelemetryError } from '../telemetry/telemetry'
4
5
  /**
5
6
  * Use POST request without content type to:
6
7
  * - avoid CORS preflight requests
@@ -9,28 +10,18 @@ import { monitor } from '../helper/monitor'
9
10
  * multiple elements are sent separated by \n in order
10
11
  * to be parsed correctly without content type header
11
12
  */
12
- function addBatchPrecision(url) {
13
+ function addBatchPrecision(url, encoding) {
13
14
  if (!url) return url
14
- return url + (url.indexOf('?') === -1 ? '?' : '&') + 'precision=ms'
15
+ url = url + (url.indexOf('?') === -1 ? '?' : '&') + 'precision=ms'
16
+ if (encoding) {
17
+ url = url + '&encoding=' + encoding
18
+ }
19
+ return url
15
20
  }
16
- export function createHttpRequest(
17
- endpointUrl,
18
- bytesLimit,
19
- sendContentTypeByJson,
20
- reportError
21
- ) {
22
- var contentType = sendContentTypeByJson
23
- ? 'application/json; charset=UTF-8'
24
- : undefined
21
+ export function createHttpRequest(endpointUrl, bytesLimit, reportError) {
25
22
  var retryState = newRetryState()
26
23
  var sendStrategyForRetry = function (payload, onResponse) {
27
- return fetchKeepAliveStrategy(
28
- endpointUrl,
29
- bytesLimit,
30
- contentType,
31
- payload,
32
- onResponse
33
- )
24
+ return fetchKeepAliveStrategy(endpointUrl, bytesLimit, payload, onResponse)
34
25
  }
35
26
 
36
27
  return {
@@ -48,22 +39,22 @@ export function createHttpRequest(
48
39
  * keep using sendBeaconStrategy on exit
49
40
  */
50
41
  sendOnExit: function (payload) {
51
- sendBeaconStrategy(endpointUrl, bytesLimit, contentType, payload)
42
+ sendBeaconStrategy(endpointUrl, bytesLimit, payload)
52
43
  }
53
44
  }
54
45
  }
55
46
 
56
- function sendBeaconStrategy(endpointUrl, bytesLimit, contentType, payload) {
47
+ function sendBeaconStrategy(endpointUrl, bytesLimit, payload) {
57
48
  var data = payload.data
58
49
  var bytesCount = payload.bytesCount
59
- var url = addBatchPrecision(endpointUrl)
50
+ var url = addBatchPrecision(endpointUrl, payload.encoding)
60
51
  var canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit
61
52
  if (canUseBeacon) {
62
53
  try {
63
54
  var beaconData
64
- if (contentType) {
55
+ if (payload.type) {
65
56
  beaconData = new Blob([data], {
66
- type: contentType
57
+ type: payload.type
67
58
  })
68
59
  } else {
69
60
  beaconData = data
@@ -75,22 +66,28 @@ function sendBeaconStrategy(endpointUrl, bytesLimit, contentType, payload) {
75
66
  return
76
67
  }
77
68
  } catch (e) {
78
- // reportBeaconError(e)
69
+ reportBeaconError(e)
79
70
  }
80
71
  }
81
- sendXHR(url, contentType, data)
72
+ sendXHR(url, payload)
82
73
  }
74
+ var hasReportedBeaconError = false
83
75
 
76
+ function reportBeaconError(e) {
77
+ if (!hasReportedBeaconError) {
78
+ hasReportedBeaconError = true
79
+ addTelemetryError(e)
80
+ }
81
+ }
84
82
  export function fetchKeepAliveStrategy(
85
83
  endpointUrl,
86
84
  bytesLimit,
87
- contentType,
88
85
  payload,
89
86
  onResponse
90
87
  ) {
91
88
  var data = payload.data
92
89
  var bytesCount = payload.bytesCount
93
- var url = addBatchPrecision(endpointUrl)
90
+ var url = addBatchPrecision(endpointUrl, payload.encoding)
94
91
  var canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit
95
92
  if (canUseKeepAlive) {
96
93
  var fetchOption = {
@@ -99,9 +96,9 @@ export function fetchKeepAliveStrategy(
99
96
  keepalive: true,
100
97
  mode: 'cors'
101
98
  }
102
- if (contentType) {
99
+ if (payload.type) {
103
100
  fetchOption.headers = {
104
- 'Content-Type': contentType
101
+ 'Content-Type': payload.type
105
102
  }
106
103
  }
107
104
  fetch(url, fetchOption).then(
@@ -112,11 +109,11 @@ export function fetchKeepAliveStrategy(
112
109
  }),
113
110
  monitor(function () {
114
111
  // failed to queue the request
115
- sendXHR(url, contentType, data, onResponse)
112
+ sendXHR(url, payload, onResponse)
116
113
  })
117
114
  )
118
115
  } else {
119
- sendXHR(url, contentType, data, onResponse)
116
+ sendXHR(url, payload, onResponse)
120
117
  }
121
118
  }
122
119
 
@@ -129,12 +126,14 @@ function isKeepAliveSupported() {
129
126
  }
130
127
  }
131
128
 
132
- function sendXHR(url, contentType, data, onResponse) {
129
+ function sendXHR(url, payload, onResponse) {
130
+ const data = payload.data
133
131
  const request = new XMLHttpRequest()
134
132
  request.open('POST', url, true)
135
- if (contentType) {
136
- //application/json; charset=UTF-8
137
- request.setRequestHeader('Content-Type', contentType)
133
+ if (data instanceof Blob) {
134
+ request.setRequestHeader('Content-Type', data.type)
135
+ } else if (payload.type) {
136
+ request.setRequestHeader('Content-Type', payload.type)
138
137
  }
139
138
 
140
139
  addEventListener(
@@ -1,5 +1,5 @@
1
1
  export { createHttpRequest } from './httpRequest'
2
- export { Batch, processedMessageByDataMap } from './batch'
2
+ export { createBatch, processedMessageByDataMap } from './batch'
3
3
  export { startBatchWithReplica } from './startBatchWithReplica'
4
4
  export { createFlushController } from './flushController'
5
5
  export { getEventBridge, canUseEventBridge } from './eventBridge'