@cloudcare/browser-core 3.2.28 → 3.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/cjs/dataMap.js +1 -0
  2. package/cjs/dataMap.js.map +1 -1
  3. package/cjs/helper/encoder.js +1 -1
  4. package/cjs/helper/encoder.js.map +1 -1
  5. package/cjs/index.js +7 -0
  6. package/cjs/index.js.map +1 -1
  7. package/cjs/session/sessionConstants.js +5 -1
  8. package/cjs/session/sessionConstants.js.map +1 -1
  9. package/cjs/session/sessionInCookie.js +1 -1
  10. package/cjs/session/sessionInCookie.js.map +1 -1
  11. package/cjs/session/sessionInLocalStorage.js +1 -1
  12. package/cjs/session/sessionInLocalStorage.js.map +1 -1
  13. package/cjs/session/sessionStore.js +18 -5
  14. package/cjs/session/sessionStore.js.map +1 -1
  15. package/cjs/session/sessionStoreOperations.js +34 -8
  16. package/cjs/session/sessionStoreOperations.js.map +1 -1
  17. package/cjs/transport/batch.js +7 -2
  18. package/cjs/transport/batch.js.map +1 -1
  19. package/cjs/transport/httpRequest.js +37 -6
  20. package/cjs/transport/httpRequest.js.map +1 -1
  21. package/esm/dataMap.js +1 -0
  22. package/esm/dataMap.js.map +1 -1
  23. package/esm/helper/encoder.js +1 -1
  24. package/esm/helper/encoder.js.map +1 -1
  25. package/esm/index.js +1 -1
  26. package/esm/index.js.map +1 -1
  27. package/esm/session/sessionConstants.js +4 -0
  28. package/esm/session/sessionConstants.js.map +1 -1
  29. package/esm/session/sessionInCookie.js +2 -2
  30. package/esm/session/sessionInCookie.js.map +1 -1
  31. package/esm/session/sessionInLocalStorage.js +2 -2
  32. package/esm/session/sessionInLocalStorage.js.map +1 -1
  33. package/esm/session/sessionStore.js +18 -5
  34. package/esm/session/sessionStore.js.map +1 -1
  35. package/esm/session/sessionStoreOperations.js +34 -9
  36. package/esm/session/sessionStoreOperations.js.map +1 -1
  37. package/esm/transport/batch.js +7 -2
  38. package/esm/transport/batch.js.map +1 -1
  39. package/esm/transport/httpRequest.js +37 -6
  40. package/esm/transport/httpRequest.js.map +1 -1
  41. package/package.json +2 -2
  42. package/src/dataMap.js +1 -0
  43. package/src/helper/encoder.js +1 -1
  44. package/src/index.js +2 -1
  45. package/src/session/sessionConstants.js +4 -0
  46. package/src/session/sessionInCookie.js +3 -2
  47. package/src/session/sessionInLocalStorage.js +4 -2
  48. package/src/session/sessionStore.js +27 -8
  49. package/src/session/sessionStoreOperations.js +31 -13
  50. package/src/transport/batch.js +1 -2
  51. package/src/transport/httpRequest.js +52 -15
  52. package/types/index.d.ts +16 -2
@@ -1,5 +1,6 @@
1
1
  import { clearInterval, setInterval } from '../helper/timer'
2
2
  import { Observable } from '../helper/observable'
3
+ import { display } from '../helper/display'
3
4
  import { ONE_SECOND, dateNow, throttle, UUID, assign } from '../helper/tools'
4
5
  import { selectCookieStrategy, initCookieStrategy } from './sessionInCookie'
5
6
  import {
@@ -13,6 +14,7 @@ import {
13
14
  selectLocalStorageStrategy
14
15
  } from './sessionInLocalStorage'
15
16
  import { processSessionStoreOperations } from './sessionStoreOperations'
17
+ import { SessionPersistence } from './sessionConstants'
16
18
 
17
19
  /**
18
20
  * Every second, the storage will be polled to check for any change that can occur
@@ -26,14 +28,31 @@ export const STORAGE_POLL_DELAY = ONE_SECOND
26
28
  * Else, checks if LocalStorage is allowed and available
27
29
  */
28
30
  export function selectSessionStoreStrategyType(initConfiguration) {
29
- let sessionStoreStrategyType = selectCookieStrategy(initConfiguration)
30
- if (
31
- !sessionStoreStrategyType &&
32
- initConfiguration.allowFallbackToLocalStorage
33
- ) {
34
- sessionStoreStrategyType = selectLocalStorageStrategy()
31
+ switch (initConfiguration.sessionPersistence) {
32
+ case SessionPersistence.COOKIE:
33
+ return selectCookieStrategy(initConfiguration)
34
+
35
+ case SessionPersistence.LOCAL_STORAGE:
36
+ return selectLocalStorageStrategy()
37
+
38
+ case undefined: {
39
+ let sessionStoreStrategyType = selectCookieStrategy(initConfiguration)
40
+ if (
41
+ !sessionStoreStrategyType &&
42
+ initConfiguration.allowFallbackToLocalStorage
43
+ ) {
44
+ sessionStoreStrategyType = selectLocalStorageStrategy()
45
+ }
46
+ return sessionStoreStrategyType
47
+ }
48
+
49
+ default:
50
+ display.error(
51
+ `Invalid session persistence '${String(
52
+ initConfiguration.sessionPersistence
53
+ )}'`
54
+ )
35
55
  }
36
- return sessionStoreStrategyType
37
56
  }
38
57
 
39
58
  /**
@@ -51,7 +70,7 @@ export function startSessionStore(
51
70
  const expireObservable = new Observable()
52
71
  const sessionStateUpdateObservable = new Observable()
53
72
  const sessionStoreStrategy =
54
- sessionStoreStrategyType.type === 'Cookie'
73
+ sessionStoreStrategyType.type === SessionPersistence.COOKIE
55
74
  ? initCookieStrategy(sessionStoreStrategyType.cookieOptions)
56
75
  : initLocalStorageStrategy()
57
76
  const { expireSession } = sessionStoreStrategy
@@ -1,13 +1,20 @@
1
1
  import { setTimeout } from '../helper/timer'
2
- import { UUID, assign } from '../helper/tools'
3
2
  import {
4
- expandSessionState,
5
- isSessionInExpiredState,
6
- isSessionInNotStartedState
7
- } from './sessionState'
8
-
3
+ UUID,
4
+ assign,
5
+ ONE_SECOND,
6
+ timeStampNow,
7
+ elapsed
8
+ } from '../helper/tools'
9
+ import { expandSessionState, isSessionInExpiredState } from './sessionState'
10
+ import { addTelemetryDebug } from '../telemetry/telemetry'
9
11
  export const LOCK_RETRY_DELAY = 10
10
12
  export const LOCK_MAX_TRIES = 100
13
+
14
+ // Locks should be hold for a few milliseconds top, just the time it takes to read and write a
15
+ // cookie. Using one second should be enough in most situations.
16
+ export const LOCK_EXPIRATION_DELAY = ONE_SECOND
17
+ const LOCK_SEPARATOR = '--'
11
18
  const bufferedOperations = []
12
19
  let ongoingOperations
13
20
 
@@ -21,15 +28,11 @@ export function processSessionStoreOperations(
21
28
  return persistSession(assign({}, session, { lock: currentLock }))
22
29
  }
23
30
  const retrieveStore = function () {
24
- const session = sessionStoreStrategy.retrieveSession()
25
- const lock = session.lock
26
- if (session.lock) {
27
- delete session.lock
28
- }
31
+ const { lock, ...session } = sessionStoreStrategy.retrieveSession()
29
32
 
30
33
  return {
31
34
  session,
32
- lock
35
+ lock: lock && !isLockExpired(lock) ? lock : undefined
33
36
  }
34
37
  }
35
38
 
@@ -41,6 +44,9 @@ export function processSessionStoreOperations(
41
44
  return
42
45
  }
43
46
  if (isLockEnabled && numberOfRetries >= LOCK_MAX_TRIES) {
47
+ addTelemetryDebug('Aborted session operation after max lock retries', {
48
+ currentStore: retrieveStore()
49
+ })
44
50
  next(sessionStoreStrategy)
45
51
  return
46
52
  }
@@ -53,7 +59,7 @@ export function processSessionStoreOperations(
53
59
  return
54
60
  }
55
61
  // acquire lock
56
- currentLock = UUID()
62
+ currentLock = createLock()
57
63
  persistWithLock(currentStore.session)
58
64
  // if lock is not acquired, retry later
59
65
  currentStore = retrieveStore()
@@ -120,3 +126,15 @@ function next(sessionStore) {
120
126
  processSessionStoreOperations(nextOperations, sessionStore)
121
127
  }
122
128
  }
129
+
130
+ export function createLock() {
131
+ return UUID() + LOCK_SEPARATOR + timeStampNow()
132
+ }
133
+
134
+ function isLockExpired(lock) {
135
+ const [, timeStamp] = lock.split(LOCK_SEPARATOR)
136
+ return (
137
+ !timeStamp ||
138
+ elapsed(Number(timeStamp), timeStampNow()) > LOCK_EXPIRATION_DELAY
139
+ )
140
+ }
@@ -234,9 +234,8 @@ export function createBatch(options) {
234
234
  if (encoderResult.outputBytesCount) {
235
235
  send(formatPayloadFromEncoder(encoderResult, sendContentTypeByJson))
236
236
  }
237
-
238
237
  // Send messages that are not yet encoded at this point
239
- var pendingMessages = [encoderResult.pendingData, upsertMessages]
238
+ var pendingMessages = [...encoderResult.pendingData, upsertMessages]
240
239
  .filter(Boolean)
241
240
  .join('\n')
242
241
 
@@ -104,22 +104,28 @@ export function fetchKeepAliveStrategy(
104
104
  keepalive: true,
105
105
  mode: 'cors'
106
106
  }
107
+ const headers = {
108
+ 'x-client-timestamp': Date.now().toString()
109
+ }
110
+
107
111
  if (payload.type) {
108
- fetchOption.headers = {
109
- 'Content-Type': payload.type
110
- }
112
+ headers['Content-Type'] = payload.type
111
113
  }
112
- fetch(url, fetchOption).then(
113
- monitor(function (response) {
114
- if (typeof onResponse === 'function') {
115
- onResponse({ status: response.status, type: response.type })
116
- }
117
- }),
118
- monitor(function () {
119
- // failed to queue the request
120
- sendXHR(url, payload, onResponse)
121
- })
122
- )
114
+
115
+ fetchOption.headers = headers
116
+ fetch(url, fetchOption)
117
+ .then(
118
+ monitor(function (response) {
119
+ if (typeof onResponse === 'function') {
120
+ onResponse({ status: response.status, type: response.type })
121
+ }
122
+ })
123
+ )
124
+ .catch(
125
+ monitor(function () {
126
+ fetchStrategy(url, payload, onResponse)
127
+ })
128
+ )
123
129
  } else {
124
130
  sendXHR(url, payload, onResponse)
125
131
  }
@@ -143,7 +149,7 @@ function sendXHR(url, payload, onResponse) {
143
149
  } else if (payload.type) {
144
150
  request.setRequestHeader('Content-Type', payload.type)
145
151
  }
146
-
152
+ request.setRequestHeader('x-client-timestamp', Date.now().toString())
147
153
  addEventListener(
148
154
  request,
149
155
  'loadend',
@@ -158,3 +164,34 @@ function sendXHR(url, payload, onResponse) {
158
164
  )
159
165
  request.send(data)
160
166
  }
167
+ function fetchStrategy(url, payload, onResponse) {
168
+ const fetchOption = {
169
+ method: 'POST',
170
+ body: payload.data,
171
+ keepalive: true,
172
+ mode: 'cors'
173
+ }
174
+ const headers = {
175
+ 'x-client-timestamp': Date.now().toString()
176
+ }
177
+ if (payload.type) {
178
+ headers['Content-Type'] = payload.type
179
+ }
180
+ fetchOption.headers = headers
181
+
182
+ fetch(url, fetchOption)
183
+ .then(
184
+ monitor(function (response) {
185
+ if (typeof onResponse === 'function') {
186
+ onResponse({ status: response.status, type: response.type })
187
+ }
188
+ })
189
+ )
190
+ .catch(
191
+ monitor(function () {
192
+ if (typeof onResponse === 'function') {
193
+ onResponse({ status: 0 })
194
+ }
195
+ })
196
+ )
197
+ }
package/types/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export interface User {
7
7
  name?: string | undefined
8
8
  [key: string]: unknown
9
9
  }
10
+ export type SessionPersistence = 'cookie' | 'local-storage'
11
+
10
12
  export declare const ConsoleApiName: {
11
13
  readonly log: 'log'
12
14
  readonly debug: 'debug'
@@ -91,6 +93,13 @@ export interface InitConfiguration {
91
93
  * When cookies are not available, you can enable this option to store data in localStorage
92
94
  */
93
95
  allowFallbackToLocalStorage?: boolean | undefined
96
+ /**
97
+ * Which storage strategy to use for persisting sessions. Can be either 'cookie' or 'local-storage'.
98
+ *
99
+ * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
100
+ * @default "cookie"
101
+ */
102
+ sessionPersistence?: SessionPersistence | undefined
94
103
  }
95
104
  export enum TraceType {
96
105
  DDTRACE = 'ddtrace',
@@ -118,7 +127,7 @@ export const NodePrivacyLevel = {
118
127
  } as const
119
128
  export type NodePrivacyLevel =
120
129
  (typeof NodePrivacyLevel)[keyof typeof NodePrivacyLevel]
121
-
130
+ export type OriginMatchOption = string | RegExp
122
131
  export type MatchOption = string | RegExp | ((value: string) => boolean)
123
132
  export type TracingOption = {
124
133
  match: MatchOption
@@ -145,11 +154,16 @@ export interface RumBaseInitConfiguration extends InitConfiguration {
145
154
  * https://docs.truewatch.com/security/page-performance/#_3
146
155
  */
147
156
  excludedActivityUrls?: MatchOption[] | undefined
157
+ /**
158
+ * List of all requests allowed to inject trace collector required headers. For example:
159
+ ["https://api.example.com/test", /https:\\/\\/.*\\.my-api-domain\\.com/, (url) => { return true }].
160
+ */
161
+ allowedTracingUrls?: Array<MatchOption | TracingOption> | undefined
148
162
  /**
149
163
  * List of all requests allowed to inject trace collector required headers. Can be the request origin or a regex. Origin: protocol (including ://), domain name (or IP address) [and port number]. For example:
150
164
  ["https://api.example.com", /https:\\/\\/.*\\.my-api-domain\\.com/].
151
165
  */
152
- allowedTracingUrls?: Array<MatchOption | TracingOption> | undefined
166
+ allowedTracingOrigins?: Array<OriginMatchOption> | undefined
153
167
  defaultPrivacyLevel?: DefaultPrivacyLevel | undefined
154
168
  /**
155
169
  * Error session compensation sample rate: