@grafana/faro-web-sdk 2.8.0 → 2.8.2

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 (50) hide show
  1. package/dist/bundle/faro-web-sdk.iife.js +1 -1
  2. package/dist/bundle/types/instrumentations/console/instrumentation.d.ts +1 -1
  3. package/dist/bundle/types/instrumentations/csp/instrumentation.d.ts +1 -1
  4. package/dist/bundle/types/instrumentations/errors/instrumentation.d.ts +1 -1
  5. package/dist/bundle/types/instrumentations/navigation/instrumentation.d.ts +1 -1
  6. package/dist/bundle/types/instrumentations/performance/instrumentation.d.ts +1 -1
  7. package/dist/bundle/types/instrumentations/session/instrumentation.d.ts +2 -1
  8. package/dist/bundle/types/instrumentations/session/sessionManager/PersistentSessionsManager.d.ts +3 -0
  9. package/dist/bundle/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +1 -0
  10. package/dist/bundle/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +2 -1
  11. package/dist/bundle/types/instrumentations/userActions/instrumentation.d.ts +1 -1
  12. package/dist/bundle/types/instrumentations/view/instrumentation.d.ts +1 -1
  13. package/dist/bundle/types/instrumentations/webVitals/instrumentation.d.ts +1 -1
  14. package/dist/bundle/types/transports/console/transport.d.ts +1 -1
  15. package/dist/bundle/types/transports/fetch/transport.d.ts +5 -1
  16. package/dist/cjs/instrumentations/session/instrumentation.js +30 -5
  17. package/dist/cjs/instrumentations/session/instrumentation.js.map +1 -1
  18. package/dist/cjs/instrumentations/session/sessionManager/PersistentSessionsManager.js +15 -0
  19. package/dist/cjs/instrumentations/session/sessionManager/PersistentSessionsManager.js.map +1 -1
  20. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js +3 -0
  21. package/dist/cjs/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  22. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js +11 -4
  23. package/dist/cjs/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  24. package/dist/cjs/transports/fetch/transport.js +77 -17
  25. package/dist/cjs/transports/fetch/transport.js.map +1 -1
  26. package/dist/esm/instrumentations/session/instrumentation.js +30 -5
  27. package/dist/esm/instrumentations/session/instrumentation.js.map +1 -1
  28. package/dist/esm/instrumentations/session/sessionManager/PersistentSessionsManager.js +15 -0
  29. package/dist/esm/instrumentations/session/sessionManager/PersistentSessionsManager.js.map +1 -1
  30. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js +3 -0
  31. package/dist/esm/instrumentations/session/sessionManager/VolatileSessionManager.js.map +1 -1
  32. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js +11 -4
  33. package/dist/esm/instrumentations/session/sessionManager/sessionManagerUtils.js.map +1 -1
  34. package/dist/esm/transports/fetch/transport.js +77 -17
  35. package/dist/esm/transports/fetch/transport.js.map +1 -1
  36. package/dist/types/instrumentations/console/instrumentation.d.ts +1 -1
  37. package/dist/types/instrumentations/csp/instrumentation.d.ts +1 -1
  38. package/dist/types/instrumentations/errors/instrumentation.d.ts +1 -1
  39. package/dist/types/instrumentations/navigation/instrumentation.d.ts +1 -1
  40. package/dist/types/instrumentations/performance/instrumentation.d.ts +1 -1
  41. package/dist/types/instrumentations/session/instrumentation.d.ts +2 -1
  42. package/dist/types/instrumentations/session/sessionManager/PersistentSessionsManager.d.ts +3 -0
  43. package/dist/types/instrumentations/session/sessionManager/VolatileSessionManager.d.ts +1 -0
  44. package/dist/types/instrumentations/session/sessionManager/sessionManagerUtils.d.ts +2 -1
  45. package/dist/types/instrumentations/userActions/instrumentation.d.ts +1 -1
  46. package/dist/types/instrumentations/view/instrumentation.d.ts +1 -1
  47. package/dist/types/instrumentations/webVitals/instrumentation.d.ts +1 -1
  48. package/dist/types/transports/console/transport.d.ts +1 -1
  49. package/dist/types/transports/fetch/transport.d.ts +5 -1
  50. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  import { BaseInstrumentation } from '@grafana/faro-core';
2
2
  export declare class UserActionInstrumentation extends BaseInstrumentation {
3
3
  readonly name = "@grafana/faro-web-sdk:instrumentation-user-action";
4
- readonly version = "2.8.0";
4
+ readonly version = "2.8.2";
5
5
  private _userActionSub?;
6
6
  initialize(): void;
7
7
  destroy(): void;
@@ -1,7 +1,7 @@
1
1
  import { BaseInstrumentation } from '@grafana/faro-core';
2
2
  export declare class ViewInstrumentation extends BaseInstrumentation {
3
3
  readonly name = "@grafana/faro-web-sdk:instrumentation-view";
4
- readonly version = "2.8.0";
4
+ readonly version = "2.8.2";
5
5
  private notifiedView;
6
6
  private sendViewChangedEvent;
7
7
  initialize(): void;
@@ -1,6 +1,6 @@
1
1
  import { BaseInstrumentation } from '@grafana/faro-core';
2
2
  export declare class WebVitalsInstrumentation extends BaseInstrumentation {
3
3
  readonly name = "@grafana/faro-web-sdk:instrumentation-web-vitals";
4
- readonly version = "2.8.0";
4
+ readonly version = "2.8.2";
5
5
  initialize(): void;
6
6
  }
@@ -4,7 +4,7 @@ import type { ConsoleTransportOptions } from './types';
4
4
  export declare class ConsoleTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-web-sdk:transport-console";
7
- readonly version = "2.8.0";
7
+ readonly version = "2.8.2";
8
8
  constructor(options?: ConsoleTransportOptions);
9
9
  send(item: TransportItem): void;
10
10
  }
@@ -4,7 +4,7 @@ import type { FetchTransportOptions } from './types';
4
4
  export declare class FetchTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-web-sdk:transport-fetch";
7
- readonly version = "2.8.0";
7
+ readonly version = "2.8.2";
8
8
  promiseBuffer: PromiseBuffer<Response | void>;
9
9
  private readonly rateLimitBackoffMs;
10
10
  private readonly getNow;
@@ -15,6 +15,10 @@ export declare class FetchTransport extends BaseTransport {
15
15
  getIgnoreUrls(): Patterns;
16
16
  isBatched(): boolean;
17
17
  private getRetryAfterDate;
18
+ private reserveKeepalive;
19
+ private fetchWithKeepaliveRetry;
20
+ private handleResponse;
21
+ private isFetchNetworkError;
18
22
  private compress;
19
23
  private extendFaroSession;
20
24
  }
@@ -11,11 +11,18 @@ class SessionInstrumentation extends faro_core_1.BaseInstrumentation {
11
11
  super(...arguments);
12
12
  this.name = '@grafana/faro-web-sdk:instrumentation-session';
13
13
  this.version = faro_core_1.VERSION;
14
+ // Reads the session manager's adoption flag (set once the manager exists).
15
+ this.isAdoptingSession = () => false;
14
16
  }
15
17
  sendSessionStartEvent(meta) {
16
18
  var _a, _b;
17
19
  const session = meta.session;
18
20
  if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
21
+ // Adopting another tab's session: track it but emit nothing (the creating tab already did).
22
+ if (this.isAdoptingSession()) {
23
+ this.notifiedSession = session;
24
+ return;
25
+ }
19
26
  if (this.notifiedSession && this.notifiedSession.id === ((_b = session.attributes) === null || _b === void 0 ? void 0 : _b['previousSession'])) {
20
27
  this.api.pushEvent(faro_core_1.EVENT_SESSION_EXTEND, {}, undefined, { skipDedupe: true });
21
28
  this.notifiedSession = session;
@@ -70,17 +77,35 @@ class SessionInstrumentation extends faro_core_1.BaseInstrumentation {
70
77
  }
71
78
  registerBeforeSendHook(SessionManager) {
72
79
  var _a;
73
- const { updateSession } = new SessionManager();
80
+ const sessionManager = new SessionManager();
81
+ this.isAdoptingSession = sessionManager.isAdopting;
82
+ const { updateSession } = sessionManager;
83
+ // Most recent rotation updateSession performed. A batch buffered before the
84
+ // rotation is all stamped with the now-expired id, not just the triggering item.
85
+ let lastRotation;
74
86
  (_a = this.transports) === null || _a === void 0 ? void 0 : _a.addBeforeSendHooks((item) => {
75
- var _a, _b, _c;
87
+ var _a, _b, _c, _d;
88
+ const previousSessionId = (_a = this.metas.value.session) === null || _a === void 0 ? void 0 : _a.id;
76
89
  updateSession();
77
- const attributes = (_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.attributes;
90
+ const currentSession = this.metas.value.session;
91
+ if (currentSession != null && previousSessionId != null && currentSession.id !== previousSessionId) {
92
+ lastRotation = { from: previousSessionId, to: currentSession };
93
+ }
94
+ // Re-stamp items still carrying the rotated-from session. Keyed on that id,
95
+ // so items from a genuinely earlier session (explicit setSession, which
96
+ // updateSession never rotates) keep their own sampling decision.
97
+ const reStamp = lastRotation != null && ((_b = item.meta.session) === null || _b === void 0 ? void 0 : _b.id) === lastRotation.from;
98
+ const session = reStamp ? lastRotation.to : item.meta.session;
99
+ const attributes = session === null || session === void 0 ? void 0 : session.attributes;
78
100
  if (attributes && (attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) === 'true') {
79
101
  let newItem = JSON.parse(JSON.stringify(item));
80
- const newAttributes = (_b = newItem.meta.session) === null || _b === void 0 ? void 0 : _b.attributes;
102
+ if (reStamp) {
103
+ newItem.meta.session = JSON.parse(JSON.stringify(lastRotation.to));
104
+ }
105
+ const newAttributes = (_c = newItem.meta.session) === null || _c === void 0 ? void 0 : _c.attributes;
81
106
  newAttributes === null || newAttributes === void 0 ? true : delete newAttributes['isSampled'];
82
107
  if (Object.keys(newAttributes !== null && newAttributes !== void 0 ? newAttributes : {}).length === 0) {
83
- (_c = newItem.meta.session) === null || _c === void 0 ? true : delete _c.attributes;
108
+ (_d = newItem.meta.session) === null || _d === void 0 ? true : delete _d.attributes;
84
109
  }
85
110
  return newItem;
86
111
  }
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/instrumentation.ts"],"names":[],"mappings":";;;AAAA,kDAO4B;AAI5B,uCAA4C;AAE5C,qDAA8F;AAC9F,0FAAuF;AACvF,8EAAmG;AAKnG,MAAa,sBAAuB,SAAQ,+BAAmB;IAA/D;;QACW,SAAI,GAAG,+CAA+C,CAAC;QACvD,YAAO,GAAG,mBAAO,CAAC;IA2J7B,CAAC;IArJS,qBAAqB,CAAC,IAAU;;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAE7B,IAAI,OAAO,IAAI,OAAO,CAAC,EAAE,MAAK,MAAA,IAAI,CAAC,eAAe,0CAAE,EAAE,CAAA,EAAE,CAAC;YACvD,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC,EAAE,MAAK,MAAA,OAAO,CAAC,UAAU,0CAAG,iBAAiB,CAAC,CAAA,EAAE,CAAC;gBAChG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,gCAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9E,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;YAC/B,8EAA8E;YAC9E,gBAAgB;YAChB,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,+BAAmB,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAEO,oBAAoB,CAC1B,cAA8B,EAC9B,cAAmD;;QAKnD,IAAI,iBAAiB,GAA2B,cAAc,CAAC,gBAAgB,EAAE,CAAC;QAElF,IAAI,cAAc,CAAC,UAAU,IAAI,cAAc,CAAC,yBAAyB,IAAI,iBAAiB,EAAE,CAAC;YAC/F,MAAM,GAAG,GAAG,IAAA,mBAAO,GAAE,CAAC;YACtB,MAAM,4BAA4B,GAChC,iBAAiB,CAAC,YAAY,GAAG,GAAG,GAAG,cAAc,CAAC,yBAAyB,CAAC;YAElF,IAAI,4BAA4B,EAAE,CAAC;gBACjC,qDAAyB,CAAC,iBAAiB,EAAE,CAAC;gBAC9C,iBAAiB,GAAG,IAAI,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,IAAI,aAA4B,CAAC;QACjC,IAAI,cAA+B,CAAC;QAEpC,IAAI,IAAA,wCAAkB,EAAC,iBAAiB,CAAC,EAAE,CAAC;YAC1C,MAAM,SAAS,GAAG,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAE,SAAS,CAAC;YAE/C,cAAc,GAAG,IAAA,6CAAuB,EAAC;gBACvC,SAAS;gBACT,SAAS,EAAE,iBAAkB,CAAC,SAAS,IAAI,KAAK;gBAChD,OAAO,EAAE,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAE,OAAO;aACpC,CAAC,CAAC;YAEH,MAAM,qBAAqB,GAAG,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAE,WAAW,CAAC;YAE7D,yFAAyF;YACzF,6EAA6E;YAC7E,MAAM,SAAS,mCAAQ,MAAA,cAAc,CAAC,OAAO,0CAAE,SAAS,GAAK,qBAAqB,aAArB,qBAAqB,uBAArB,qBAAqB,CAAE,SAAS,CAAE,CAAC;YAEhG,cAAc,CAAC,WAAW,mCACrB,cAAc,CAAC,OAAO,KACzB,EAAE,EAAE,SAAS,EACb,UAAU,gDACL,MAAA,cAAc,CAAC,OAAO,0CAAE,UAAU,GAClC,qBAAqB,aAArB,qBAAqB,uBAArB,qBAAqB,CAAE,UAAU;oBACpC,qGAAqG;oBACrG,SAAS,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,KAEhD,SAAS,GACV,CAAC;YAEF,aAAa,GAAG,gCAAoB,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,MAAM,SAAS,GAAG,MAAA,MAAA,cAAc,CAAC,OAAO,0CAAE,EAAE,mCAAI,IAAA,qBAAa,GAAE,CAAC,EAAE,CAAC;YAEnE,cAAc,GAAG,IAAA,6CAAuB,EAAC;gBACvC,SAAS;gBACT,SAAS,EAAE,IAAA,0BAAS,GAAE;aACvB,CAAC,CAAC;YAEH,MAAM,SAAS,GAAG,MAAA,cAAc,CAAC,OAAO,0CAAE,SAAS,CAAC;YAEpD,cAAc,CAAC,WAAW,mBACxB,EAAE,EAAE,SAAS,EACb,UAAU,kBACR,SAAS,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,IAC3C,MAAA,cAAc,CAAC,OAAO,0CAAE,UAAU,KAGpC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpC,CAAC;YAEF,aAAa,GAAG,+BAAmB,CAAC;QACtC,CAAC;QAED,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IAC3C,CAAC;IAEO,sBAAsB,CAAC,cAA8B;;QAC3D,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;QAE/C,MAAA,IAAI,CAAC,UAAU,0CAAE,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE;;YAC3C,aAAa,EAAE,CAAC;YAEhB,MAAM,UAAU,GAAG,MAAA,IAAI,CAAC,IAAI,CAAC,OAAO,0CAAE,UAAU,CAAC;YAEjD,IAAI,UAAU,IAAI,CAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAG,WAAW,CAAC,MAAK,MAAM,EAAE,CAAC;gBACvD,IAAI,OAAO,GAAkB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBAE9D,MAAM,aAAa,GAAG,MAAA,OAAO,CAAC,IAAI,CAAC,OAAO,0CAAE,UAAU,CAAC;gBAChD,aAAa,aAAb,aAAa,4BAAb,aAAa,CAAG,WAAW,CAAC,CAAC;gBAEpC,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC3C,MAAA,OAAO,CAAC,IAAI,CAAC,OAAO,+CAAE,UAAU,CAAC;gBAC1C,CAAC;gBAED,OAAO,OAAO,CAAC;YACjB,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,IAAI,CAAC,QAAQ,CAAC,8BAA8B,CAAC,CAAC;QAE9C,MAAM,qBAAqB,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;QAE1D,IAAI,qBAAqB,aAArB,qBAAqB,uBAArB,qBAAqB,CAAE,OAAO,EAAE,CAAC;YACnC,MAAM,cAAc,GAAG,IAAA,0CAAyB,EAAC,qBAAqB,CAAC,CAAC;YAExE,IAAI,CAAC,sBAAsB,CAAC,cAAc,CAAC,CAAC;YAE5C,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,oBAAoB,CAAC,cAAc,EAAE,qBAAqB,CAAC,CAAC;YAE3G,cAAc,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;YAEhD,MAAM,kBAAkB,GAAG,cAAc,CAAC,WAAW,CAAC;YAEtD,IAAI,CAAC,eAAe,GAAG,kBAAkB,CAAC;YAC1C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC;YAExC,IAAI,aAAa,KAAK,+BAAmB,EAAE,CAAC;gBAC1C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,+BAAmB,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/E,CAAC;YAED,IAAI,aAAa,KAAK,gCAAoB,EAAE,CAAC;gBAC3C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,gCAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;CACF;AA7JD,wDA6JC","sourcesContent":["import {\n BaseInstrumentation,\n dateNow,\n EVENT_SESSION_EXTEND,\n EVENT_SESSION_RESUME,\n EVENT_SESSION_START,\n VERSION,\n} from '@grafana/faro-core';\nimport type { Config, Meta, MetaSession } from '@grafana/faro-core';\n\nimport type { TransportItem } from '../..';\nimport { createSession } from '../../metas';\n\nimport { type FaroUserSession, getSessionManagerByConfig, isSampled } from './sessionManager';\nimport { PersistentSessionsManager } from './sessionManager/PersistentSessionsManager';\nimport { createUserSessionObject, isUserSessionValid } from './sessionManager/sessionManagerUtils';\nimport type { SessionManager } from './sessionManager/types';\n\ntype LifecycleType = typeof EVENT_SESSION_RESUME | typeof EVENT_SESSION_START;\n\nexport class SessionInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-web-sdk:instrumentation-session';\n readonly version = VERSION;\n\n // previously notified session, to ensure we don't send session start\n // event twice for the same session\n private notifiedSession: MetaSession | undefined;\n\n private sendSessionStartEvent(meta: Meta): void {\n const session = meta.session;\n\n if (session && session.id !== this.notifiedSession?.id) {\n if (this.notifiedSession && this.notifiedSession.id === session.attributes?.['previousSession']) {\n this.api.pushEvent(EVENT_SESSION_EXTEND, {}, undefined, { skipDedupe: true });\n this.notifiedSession = session;\n return;\n }\n\n this.notifiedSession = session;\n // no need to add attributes and session id, they are included as part of meta\n // automatically\n this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });\n }\n }\n\n private createInitialSession(\n SessionManager: SessionManager,\n sessionsConfig: Required<Config>['sessionTracking']\n ): {\n initialSession: FaroUserSession;\n lifecycleType: LifecycleType;\n } {\n let storedUserSession: FaroUserSession | null = SessionManager.fetchUserSession();\n\n if (sessionsConfig.persistent && sessionsConfig.maxSessionPersistenceTime && storedUserSession) {\n const now = dateNow();\n const shouldClearPersistentSession =\n storedUserSession.lastActivity < now - sessionsConfig.maxSessionPersistenceTime;\n\n if (shouldClearPersistentSession) {\n PersistentSessionsManager.removeUserSession();\n storedUserSession = null;\n }\n }\n\n let lifecycleType: LifecycleType;\n let initialSession: FaroUserSession;\n\n if (isUserSessionValid(storedUserSession)) {\n const sessionId = storedUserSession?.sessionId;\n\n initialSession = createUserSessionObject({\n sessionId,\n isSampled: storedUserSession!.isSampled || false,\n started: storedUserSession?.started,\n });\n\n const storedUserSessionMeta = storedUserSession?.sessionMeta;\n\n // For resumed sessions we want to merge the previous overrides with the configured ones.\n // If the same key is present in both, the new one will override the old one.\n const overrides = { ...sessionsConfig.session?.overrides, ...storedUserSessionMeta?.overrides };\n\n initialSession.sessionMeta = {\n ...sessionsConfig.session,\n id: sessionId,\n attributes: {\n ...sessionsConfig.session?.attributes,\n ...storedUserSessionMeta?.attributes,\n // For valid resumed sessions we do not want to recalculate the sampling decision on each init phase.\n isSampled: initialSession.isSampled.toString(),\n },\n overrides,\n };\n\n lifecycleType = EVENT_SESSION_RESUME;\n } else {\n const sessionId = sessionsConfig.session?.id ?? createSession().id;\n\n initialSession = createUserSessionObject({\n sessionId,\n isSampled: isSampled(),\n });\n\n const overrides = sessionsConfig.session?.overrides;\n\n initialSession.sessionMeta = {\n id: sessionId,\n attributes: {\n isSampled: initialSession.isSampled.toString(),\n ...sessionsConfig.session?.attributes,\n },\n // new session we don't care about previous overrides\n ...(overrides ? { overrides } : {}),\n };\n\n lifecycleType = EVENT_SESSION_START;\n }\n\n return { initialSession, lifecycleType };\n }\n\n private registerBeforeSendHook(SessionManager: SessionManager) {\n const { updateSession } = new SessionManager();\n\n this.transports?.addBeforeSendHooks((item) => {\n updateSession();\n\n const attributes = item.meta.session?.attributes;\n\n if (attributes && attributes?.['isSampled'] === 'true') {\n let newItem: TransportItem = JSON.parse(JSON.stringify(item));\n\n const newAttributes = newItem.meta.session?.attributes;\n delete newAttributes?.['isSampled'];\n\n if (Object.keys(newAttributes ?? {}).length === 0) {\n delete newItem.meta.session?.attributes;\n }\n\n return newItem;\n }\n\n return null;\n });\n }\n\n initialize() {\n this.logDebug('init session instrumentation');\n\n const sessionTrackingConfig = this.config.sessionTracking;\n\n if (sessionTrackingConfig?.enabled) {\n const SessionManager = getSessionManagerByConfig(sessionTrackingConfig);\n\n this.registerBeforeSendHook(SessionManager);\n\n const { initialSession, lifecycleType } = this.createInitialSession(SessionManager, sessionTrackingConfig);\n\n SessionManager.storeUserSession(initialSession);\n\n const initialSessionMeta = initialSession.sessionMeta;\n\n this.notifiedSession = initialSessionMeta;\n this.api.setSession(initialSessionMeta);\n\n if (lifecycleType === EVENT_SESSION_START) {\n this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });\n }\n\n if (lifecycleType === EVENT_SESSION_RESUME) {\n this.api.pushEvent(EVENT_SESSION_RESUME, {}, undefined, { skipDedupe: true });\n }\n }\n\n this.metas.addListener(this.sendSessionStartEvent.bind(this));\n }\n}\n"]}
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/session/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseInstrumentation,\n dateNow,\n EVENT_SESSION_EXTEND,\n EVENT_SESSION_RESUME,\n EVENT_SESSION_START,\n VERSION,\n} from '@grafana/faro-core';\nimport type { Config, Meta, MetaSession } from '@grafana/faro-core';\n\nimport type { TransportItem } from '../..';\nimport { createSession } from '../../metas';\n\nimport { type FaroUserSession, getSessionManagerByConfig, isSampled } from './sessionManager';\nimport { PersistentSessionsManager } from './sessionManager/PersistentSessionsManager';\nimport { createUserSessionObject, isUserSessionValid } from './sessionManager/sessionManagerUtils';\nimport type { SessionManager } from './sessionManager/types';\n\ntype LifecycleType = typeof EVENT_SESSION_RESUME | typeof EVENT_SESSION_START;\n\nexport class SessionInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-web-sdk:instrumentation-session';\n readonly version = VERSION;\n\n // previously notified session, to ensure we don't send session start\n // event twice for the same session\n private notifiedSession: MetaSession | undefined;\n\n // Reads the session manager's adoption flag (set once the manager exists).\n private isAdoptingSession: () => boolean = () => false;\n\n private sendSessionStartEvent(meta: Meta): void {\n const session = meta.session;\n\n if (session && session.id !== this.notifiedSession?.id) {\n // Adopting another tab's session: track it but emit nothing (the creating tab already did).\n if (this.isAdoptingSession()) {\n this.notifiedSession = session;\n return;\n }\n\n if (this.notifiedSession && this.notifiedSession.id === session.attributes?.['previousSession']) {\n this.api.pushEvent(EVENT_SESSION_EXTEND, {}, undefined, { skipDedupe: true });\n this.notifiedSession = session;\n return;\n }\n\n this.notifiedSession = session;\n // no need to add attributes and session id, they are included as part of meta\n // automatically\n this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });\n }\n }\n\n private createInitialSession(\n SessionManager: SessionManager,\n sessionsConfig: Required<Config>['sessionTracking']\n ): {\n initialSession: FaroUserSession;\n lifecycleType: LifecycleType;\n } {\n let storedUserSession: FaroUserSession | null = SessionManager.fetchUserSession();\n\n if (sessionsConfig.persistent && sessionsConfig.maxSessionPersistenceTime && storedUserSession) {\n const now = dateNow();\n const shouldClearPersistentSession =\n storedUserSession.lastActivity < now - sessionsConfig.maxSessionPersistenceTime;\n\n if (shouldClearPersistentSession) {\n PersistentSessionsManager.removeUserSession();\n storedUserSession = null;\n }\n }\n\n let lifecycleType: LifecycleType;\n let initialSession: FaroUserSession;\n\n if (isUserSessionValid(storedUserSession)) {\n const sessionId = storedUserSession?.sessionId;\n\n initialSession = createUserSessionObject({\n sessionId,\n isSampled: storedUserSession!.isSampled || false,\n started: storedUserSession?.started,\n });\n\n const storedUserSessionMeta = storedUserSession?.sessionMeta;\n\n // For resumed sessions we want to merge the previous overrides with the configured ones.\n // If the same key is present in both, the new one will override the old one.\n const overrides = { ...sessionsConfig.session?.overrides, ...storedUserSessionMeta?.overrides };\n\n initialSession.sessionMeta = {\n ...sessionsConfig.session,\n id: sessionId,\n attributes: {\n ...sessionsConfig.session?.attributes,\n ...storedUserSessionMeta?.attributes,\n // For valid resumed sessions we do not want to recalculate the sampling decision on each init phase.\n isSampled: initialSession.isSampled.toString(),\n },\n overrides,\n };\n\n lifecycleType = EVENT_SESSION_RESUME;\n } else {\n const sessionId = sessionsConfig.session?.id ?? createSession().id;\n\n initialSession = createUserSessionObject({\n sessionId,\n isSampled: isSampled(),\n });\n\n const overrides = sessionsConfig.session?.overrides;\n\n initialSession.sessionMeta = {\n id: sessionId,\n attributes: {\n isSampled: initialSession.isSampled.toString(),\n ...sessionsConfig.session?.attributes,\n },\n // new session we don't care about previous overrides\n ...(overrides ? { overrides } : {}),\n };\n\n lifecycleType = EVENT_SESSION_START;\n }\n\n return { initialSession, lifecycleType };\n }\n\n private registerBeforeSendHook(SessionManager: SessionManager) {\n const sessionManager = new SessionManager();\n this.isAdoptingSession = sessionManager.isAdopting;\n const { updateSession } = sessionManager;\n\n // Most recent rotation updateSession performed. A batch buffered before the\n // rotation is all stamped with the now-expired id, not just the triggering item.\n let lastRotation: { from: string; to: MetaSession } | undefined;\n\n this.transports?.addBeforeSendHooks((item) => {\n const previousSessionId = this.metas.value.session?.id;\n updateSession();\n const currentSession = this.metas.value.session;\n\n if (currentSession != null && previousSessionId != null && currentSession.id !== previousSessionId) {\n lastRotation = { from: previousSessionId, to: currentSession };\n }\n\n // Re-stamp items still carrying the rotated-from session. Keyed on that id,\n // so items from a genuinely earlier session (explicit setSession, which\n // updateSession never rotates) keep their own sampling decision.\n const reStamp = lastRotation != null && item.meta.session?.id === lastRotation.from;\n const session = reStamp ? lastRotation!.to : item.meta.session;\n\n const attributes = session?.attributes;\n\n if (attributes && attributes?.['isSampled'] === 'true') {\n let newItem: TransportItem = JSON.parse(JSON.stringify(item));\n\n if (reStamp) {\n newItem.meta.session = JSON.parse(JSON.stringify(lastRotation!.to));\n }\n\n const newAttributes = newItem.meta.session?.attributes;\n delete newAttributes?.['isSampled'];\n\n if (Object.keys(newAttributes ?? {}).length === 0) {\n delete newItem.meta.session?.attributes;\n }\n\n return newItem;\n }\n\n return null;\n });\n }\n\n initialize() {\n this.logDebug('init session instrumentation');\n\n const sessionTrackingConfig = this.config.sessionTracking;\n\n if (sessionTrackingConfig?.enabled) {\n const SessionManager = getSessionManagerByConfig(sessionTrackingConfig);\n\n this.registerBeforeSendHook(SessionManager);\n\n const { initialSession, lifecycleType } = this.createInitialSession(SessionManager, sessionTrackingConfig);\n\n SessionManager.storeUserSession(initialSession);\n\n const initialSessionMeta = initialSession.sessionMeta;\n\n this.notifiedSession = initialSessionMeta;\n this.api.setSession(initialSessionMeta);\n\n if (lifecycleType === EVENT_SESSION_START) {\n this.api.pushEvent(EVENT_SESSION_START, {}, undefined, { skipDedupe: true });\n }\n\n if (lifecycleType === EVENT_SESSION_RESUME) {\n this.api.pushEvent(EVENT_SESSION_RESUME, {}, undefined, { skipDedupe: true });\n }\n }\n\n this.metas.addListener(this.sendSessionStartEvent.bind(this));\n }\n}\n"]}
@@ -8,10 +8,25 @@ const sessionConstants_1 = require("./sessionConstants");
8
8
  const sessionManagerUtils_1 = require("./sessionManagerUtils");
9
9
  class PersistentSessionsManager {
10
10
  constructor() {
11
+ // Set only for the synchronous span of an adopting setSession(); the session
12
+ // instrumentation reads isAdopting() to suppress its lifecycle event.
13
+ this.adopting = false;
14
+ this.isAdopting = () => this.adopting;
15
+ this.adoptSession = (sessionMeta) => {
16
+ var _a;
17
+ this.adopting = true;
18
+ try {
19
+ (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.setSession(sessionMeta);
20
+ }
21
+ finally {
22
+ this.adopting = false;
23
+ }
24
+ };
11
25
  this.updateSession = (0, utils_1.throttle)(() => this.updateUserSession(), sessionConstants_1.STORAGE_UPDATE_DELAY);
12
26
  this.updateUserSession = (0, sessionManagerUtils_1.getUserSessionUpdater)({
13
27
  fetchUserSession: PersistentSessionsManager.fetchUserSession,
14
28
  storeUserSession: PersistentSessionsManager.storeUserSession,
29
+ adoptSession: this.adoptSession,
15
30
  });
16
31
  this.init();
17
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PersistentSessionsManager.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/PersistentSessionsManager.ts"],"names":[],"mappings":";;;AAAA,kDAAiE;AAEjE,0CAA0C;AAC1C,0DAAyF;AAEzF,yDAAuE;AACvE,+DAA2F;AAG3F,MAAa,yBAAyB;IAIpC;QA2BA,kBAAa,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,uCAAoB,CAAC,CAAC;QA1B7E,IAAI,CAAC,iBAAiB,GAAG,IAAA,2CAAqB,EAAC;YAC7C,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;YAC5D,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;SAC7D,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,iBAAiB;QACtB,IAAA,uBAAU,EAAC,8BAAW,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAwB;QAC9C,IAAA,oBAAO,EAAC,8BAAW,EAAE,IAAA,iCAAqB,EAAC,OAAO,CAAC,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,CAAC,gBAAgB;QACrB,MAAM,aAAa,GAAG,IAAA,oBAAO,EAAC,8BAAW,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;QAEvF,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAoB,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAIO,IAAI;QACV,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,iGAAiG;QACjG,gBAAI,CAAC,KAAK,CAAC,WAAW,CACpB,IAAA,iDAA2B,EAAC;YAC1B,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;YAC5D,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;SAC7D,CAAC,CACH,CAAC;IACJ,CAAC;;AA/CH,8DAgDC;AA/CgB,0CAAgB,GAAG,2BAAc,CAAC,KAAK,AAAvB,CAAwB","sourcesContent":["import { faro, stringifyExternalJson } from '@grafana/faro-core';\n\nimport { throttle } from '../../../utils';\nimport { getItem, removeItem, setItem, webStorageType } from '../../../utils/webStorage';\n\nimport { STORAGE_KEY, STORAGE_UPDATE_DELAY } from './sessionConstants';\nimport { getSessionMetaUpdateHandler, getUserSessionUpdater } from './sessionManagerUtils';\nimport type { FaroUserSession } from './types';\n\nexport class PersistentSessionsManager {\n private static storageTypeLocal = webStorageType.local;\n private updateUserSession: ReturnType<typeof getUserSessionUpdater>;\n\n constructor() {\n this.updateUserSession = getUserSessionUpdater({\n fetchUserSession: PersistentSessionsManager.fetchUserSession,\n storeUserSession: PersistentSessionsManager.storeUserSession,\n });\n\n this.init();\n }\n\n static removeUserSession() {\n removeItem(STORAGE_KEY, PersistentSessionsManager.storageTypeLocal);\n }\n\n static storeUserSession(session: FaroUserSession): void {\n setItem(STORAGE_KEY, stringifyExternalJson(session), PersistentSessionsManager.storageTypeLocal);\n }\n\n static fetchUserSession(): FaroUserSession | null {\n const storedSession = getItem(STORAGE_KEY, PersistentSessionsManager.storageTypeLocal);\n\n if (storedSession) {\n return JSON.parse(storedSession) as FaroUserSession;\n }\n\n return null;\n }\n\n updateSession = throttle(() => this.updateUserSession(), STORAGE_UPDATE_DELAY);\n\n private init(): void {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n this.updateSession();\n }\n });\n\n // Users can call the setSession() method, so we need to sync this with the local storage session\n faro.metas.addListener(\n getSessionMetaUpdateHandler({\n fetchUserSession: PersistentSessionsManager.fetchUserSession,\n storeUserSession: PersistentSessionsManager.storeUserSession,\n })\n );\n }\n}\n"]}
1
+ {"version":3,"file":"PersistentSessionsManager.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/PersistentSessionsManager.ts"],"names":[],"mappings":";;;AAAA,kDAAiE;AAGjE,0CAA0C;AAC1C,0DAAyF;AAEzF,yDAAuE;AACvE,+DAA2F;AAG3F,MAAa,yBAAyB;IAmBpC;QAfA,6EAA6E;QAC7E,sEAAsE;QAC9D,aAAQ,GAAG,KAAK,CAAC;QAEzB,eAAU,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;QAElC,iBAAY,GAAG,CAAC,WAAwB,EAAQ,EAAE;;YACxD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC;gBACH,MAAA,gBAAI,CAAC,GAAG,0CAAE,UAAU,CAAC,WAAW,CAAC,CAAC;YACpC,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACxB,CAAC;QACH,CAAC,CAAC;QA8BF,kBAAa,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,uCAAoB,CAAC,CAAC;QA3B7E,IAAI,CAAC,iBAAiB,GAAG,IAAA,2CAAqB,EAAC;YAC7C,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;YAC5D,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;YAC5D,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,iBAAiB;QACtB,IAAA,uBAAU,EAAC,8BAAW,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAwB;QAC9C,IAAA,oBAAO,EAAC,8BAAW,EAAE,IAAA,iCAAqB,EAAC,OAAO,CAAC,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,CAAC,gBAAgB;QACrB,MAAM,aAAa,GAAG,IAAA,oBAAO,EAAC,8BAAW,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;QAEvF,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAoB,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAIO,IAAI;QACV,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,iGAAiG;QACjG,gBAAI,CAAC,KAAK,CAAC,WAAW,CACpB,IAAA,iDAA2B,EAAC;YAC1B,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;YAC5D,gBAAgB,EAAE,yBAAyB,CAAC,gBAAgB;SAC7D,CAAC,CACH,CAAC;IACJ,CAAC;;AA/DH,8DAgEC;AA/DgB,0CAAgB,GAAG,2BAAc,CAAC,KAAK,AAAvB,CAAwB","sourcesContent":["import { faro, stringifyExternalJson } from '@grafana/faro-core';\nimport type { MetaSession } from '@grafana/faro-core';\n\nimport { throttle } from '../../../utils';\nimport { getItem, removeItem, setItem, webStorageType } from '../../../utils/webStorage';\n\nimport { STORAGE_KEY, STORAGE_UPDATE_DELAY } from './sessionConstants';\nimport { getSessionMetaUpdateHandler, getUserSessionUpdater } from './sessionManagerUtils';\nimport type { FaroUserSession } from './types';\n\nexport class PersistentSessionsManager {\n private static storageTypeLocal = webStorageType.local;\n private updateUserSession: ReturnType<typeof getUserSessionUpdater>;\n\n // Set only for the synchronous span of an adopting setSession(); the session\n // instrumentation reads isAdopting() to suppress its lifecycle event.\n private adopting = false;\n\n isAdopting = (): boolean => this.adopting;\n\n private adoptSession = (sessionMeta: MetaSession): void => {\n this.adopting = true;\n try {\n faro.api?.setSession(sessionMeta);\n } finally {\n this.adopting = false;\n }\n };\n\n constructor() {\n this.updateUserSession = getUserSessionUpdater({\n fetchUserSession: PersistentSessionsManager.fetchUserSession,\n storeUserSession: PersistentSessionsManager.storeUserSession,\n adoptSession: this.adoptSession,\n });\n\n this.init();\n }\n\n static removeUserSession() {\n removeItem(STORAGE_KEY, PersistentSessionsManager.storageTypeLocal);\n }\n\n static storeUserSession(session: FaroUserSession): void {\n setItem(STORAGE_KEY, stringifyExternalJson(session), PersistentSessionsManager.storageTypeLocal);\n }\n\n static fetchUserSession(): FaroUserSession | null {\n const storedSession = getItem(STORAGE_KEY, PersistentSessionsManager.storageTypeLocal);\n\n if (storedSession) {\n return JSON.parse(storedSession) as FaroUserSession;\n }\n\n return null;\n }\n\n updateSession = throttle(() => this.updateUserSession(), STORAGE_UPDATE_DELAY);\n\n private init(): void {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n this.updateSession();\n }\n });\n\n // Users can call the setSession() method, so we need to sync this with the local storage session\n faro.metas.addListener(\n getSessionMetaUpdateHandler({\n fetchUserSession: PersistentSessionsManager.fetchUserSession,\n storeUserSession: PersistentSessionsManager.storeUserSession,\n })\n );\n }\n}\n"]}
@@ -8,6 +8,9 @@ const sessionConstants_1 = require("./sessionConstants");
8
8
  const sessionManagerUtils_1 = require("./sessionManagerUtils");
9
9
  class VolatileSessionsManager {
10
10
  constructor() {
11
+ // sessionStorage is tab-local, so this manager never adopts another tab's
12
+ // session. Stubbed so the instrumentation can treat both managers uniformly.
13
+ this.isAdopting = () => false;
11
14
  this.updateSession = (0, utils_1.throttle)(() => this.updateUserSession(), sessionConstants_1.STORAGE_UPDATE_DELAY);
12
15
  this.updateUserSession = (0, sessionManagerUtils_1.getUserSessionUpdater)({
13
16
  fetchUserSession: VolatileSessionsManager.fetchUserSession,
@@ -1 +1 @@
1
- {"version":3,"file":"VolatileSessionManager.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/VolatileSessionManager.ts"],"names":[],"mappings":";;;AAAA,kDAAiE;AAEjE,0CAA0C;AAC1C,0DAAyF;AAEzF,yDAAuE;AACvE,+DAA2F;AAG3F,MAAa,uBAAuB;IAIlC;QA2BA,kBAAa,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,uCAAoB,CAAC,CAAC;QA1B7E,IAAI,CAAC,iBAAiB,GAAG,IAAA,2CAAqB,EAAC;YAC7C,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;YAC1D,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;SAC3D,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,iBAAiB;QACtB,IAAA,uBAAU,EAAC,8BAAW,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAwB;QAC9C,IAAA,oBAAO,EAAC,8BAAW,EAAE,IAAA,iCAAqB,EAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,CAAC,gBAAgB;QACrB,MAAM,aAAa,GAAG,IAAA,oBAAO,EAAC,8BAAW,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;QAEvF,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAoB,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAIO,IAAI;QACV,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,iGAAiG;QACjG,gBAAI,CAAC,KAAK,CAAC,WAAW,CACpB,IAAA,iDAA2B,EAAC;YAC1B,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;YAC1D,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;SAC3D,CAAC,CACH,CAAC;IACJ,CAAC;;AA/CH,0DAgDC;AA/CgB,0CAAkB,GAAG,2BAAc,CAAC,OAAO,AAAzB,CAA0B","sourcesContent":["import { faro, stringifyExternalJson } from '@grafana/faro-core';\n\nimport { throttle } from '../../../utils';\nimport { getItem, removeItem, setItem, webStorageType } from '../../../utils/webStorage';\n\nimport { STORAGE_KEY, STORAGE_UPDATE_DELAY } from './sessionConstants';\nimport { getSessionMetaUpdateHandler, getUserSessionUpdater } from './sessionManagerUtils';\nimport type { FaroUserSession } from './types';\n\nexport class VolatileSessionsManager {\n private static storageTypeSession = webStorageType.session;\n private updateUserSession: ReturnType<typeof getUserSessionUpdater>;\n\n constructor() {\n this.updateUserSession = getUserSessionUpdater({\n fetchUserSession: VolatileSessionsManager.fetchUserSession,\n storeUserSession: VolatileSessionsManager.storeUserSession,\n });\n\n this.init();\n }\n\n static removeUserSession() {\n removeItem(STORAGE_KEY, VolatileSessionsManager.storageTypeSession);\n }\n\n static storeUserSession(session: FaroUserSession): void {\n setItem(STORAGE_KEY, stringifyExternalJson(session), VolatileSessionsManager.storageTypeSession);\n }\n\n static fetchUserSession(): FaroUserSession | null {\n const storedSession = getItem(STORAGE_KEY, VolatileSessionsManager.storageTypeSession);\n\n if (storedSession) {\n return JSON.parse(storedSession) as FaroUserSession;\n }\n\n return null;\n }\n\n updateSession = throttle(() => this.updateUserSession(), STORAGE_UPDATE_DELAY);\n\n private init(): void {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n this.updateSession();\n }\n });\n\n // Users can call the setSession() method, so we need to sync this with the local storage session\n faro.metas.addListener(\n getSessionMetaUpdateHandler({\n fetchUserSession: VolatileSessionsManager.fetchUserSession,\n storeUserSession: VolatileSessionsManager.storeUserSession,\n })\n );\n }\n}\n"]}
1
+ {"version":3,"file":"VolatileSessionManager.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/VolatileSessionManager.ts"],"names":[],"mappings":";;;AAAA,kDAAiE;AAEjE,0CAA0C;AAC1C,0DAAyF;AAEzF,yDAAuE;AACvE,+DAA2F;AAG3F,MAAa,uBAAuB;IAQlC;QAJA,0EAA0E;QAC1E,6EAA6E;QAC7E,eAAU,GAAG,GAAY,EAAE,CAAC,KAAK,CAAC;QA6BlC,kBAAa,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,uCAAoB,CAAC,CAAC;QA1B7E,IAAI,CAAC,iBAAiB,GAAG,IAAA,2CAAqB,EAAC;YAC7C,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;YAC1D,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;SAC3D,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,iBAAiB;QACtB,IAAA,uBAAU,EAAC,8BAAW,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAwB;QAC9C,IAAA,oBAAO,EAAC,8BAAW,EAAE,IAAA,iCAAqB,EAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,CAAC,gBAAgB;QACrB,MAAM,aAAa,GAAG,IAAA,oBAAO,EAAC,8BAAW,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;QAEvF,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAoB,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAIO,IAAI;QACV,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACjD,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;gBAC3C,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,iGAAiG;QACjG,gBAAI,CAAC,KAAK,CAAC,WAAW,CACpB,IAAA,iDAA2B,EAAC;YAC1B,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;YAC1D,gBAAgB,EAAE,uBAAuB,CAAC,gBAAgB;SAC3D,CAAC,CACH,CAAC;IACJ,CAAC;;AAnDH,0DAoDC;AAnDgB,0CAAkB,GAAG,2BAAc,CAAC,OAAO,AAAzB,CAA0B","sourcesContent":["import { faro, stringifyExternalJson } from '@grafana/faro-core';\n\nimport { throttle } from '../../../utils';\nimport { getItem, removeItem, setItem, webStorageType } from '../../../utils/webStorage';\n\nimport { STORAGE_KEY, STORAGE_UPDATE_DELAY } from './sessionConstants';\nimport { getSessionMetaUpdateHandler, getUserSessionUpdater } from './sessionManagerUtils';\nimport type { FaroUserSession } from './types';\n\nexport class VolatileSessionsManager {\n private static storageTypeSession = webStorageType.session;\n private updateUserSession: ReturnType<typeof getUserSessionUpdater>;\n\n // sessionStorage is tab-local, so this manager never adopts another tab's\n // session. Stubbed so the instrumentation can treat both managers uniformly.\n isAdopting = (): boolean => false;\n\n constructor() {\n this.updateUserSession = getUserSessionUpdater({\n fetchUserSession: VolatileSessionsManager.fetchUserSession,\n storeUserSession: VolatileSessionsManager.storeUserSession,\n });\n\n this.init();\n }\n\n static removeUserSession() {\n removeItem(STORAGE_KEY, VolatileSessionsManager.storageTypeSession);\n }\n\n static storeUserSession(session: FaroUserSession): void {\n setItem(STORAGE_KEY, stringifyExternalJson(session), VolatileSessionsManager.storageTypeSession);\n }\n\n static fetchUserSession(): FaroUserSession | null {\n const storedSession = getItem(STORAGE_KEY, VolatileSessionsManager.storageTypeSession);\n\n if (storedSession) {\n return JSON.parse(storedSession) as FaroUserSession;\n }\n\n return null;\n }\n\n updateSession = throttle(() => this.updateUserSession(), STORAGE_UPDATE_DELAY);\n\n private init(): void {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n this.updateSession();\n }\n });\n\n // Users can call the setSession() method, so we need to sync this with the local storage session\n faro.metas.addListener(\n getSessionMetaUpdateHandler({\n fetchUserSession: VolatileSessionsManager.fetchUserSession,\n storeUserSession: VolatileSessionsManager.storeUserSession,\n })\n );\n }\n}\n"]}
@@ -35,9 +35,9 @@ function isUserSessionValid(session) {
35
35
  const inactivityPeriodValid = now - session.lastActivity < sessionConstants_1.SESSION_INACTIVITY_TIME;
36
36
  return inactivityPeriodValid;
37
37
  }
38
- function getUserSessionUpdater({ fetchUserSession, storeUserSession, }) {
38
+ function getUserSessionUpdater({ fetchUserSession, storeUserSession, adoptSession, }) {
39
39
  return function updateSession({ forceSessionExtend } = { forceSessionExtend: false }) {
40
- var _a, _b, _c;
40
+ var _a, _b, _c, _d;
41
41
  if (!fetchUserSession || !storeUserSession) {
42
42
  return;
43
43
  }
@@ -49,12 +49,19 @@ function getUserSessionUpdater({ fetchUserSession, storeUserSession, }) {
49
49
  const sessionFromStorage = fetchUserSession();
50
50
  if (forceSessionExtend === false && isUserSessionValid(sessionFromStorage)) {
51
51
  storeUserSession(Object.assign(Object.assign({}, sessionFromStorage), { lastActivity: (0, faro_core_1.dateNow)() }));
52
+ // Another tab rotated the shared session; adopt it so we stop emitting the stale id.
53
+ const inMemorySessionId = (_a = faro_core_1.faro.metas.value.session) === null || _a === void 0 ? void 0 : _a.id;
54
+ if (adoptSession != null &&
55
+ sessionFromStorage.sessionMeta != null &&
56
+ sessionFromStorage.sessionId !== inMemorySessionId) {
57
+ adoptSession(sessionFromStorage.sessionMeta);
58
+ }
52
59
  }
53
60
  else {
54
61
  let newSession = addSessionMetadataToNextSession(createUserSessionObject({ isSampled: (0, sampling_1.isSampled)() }), sessionFromStorage);
55
62
  storeUserSession(newSession);
56
- (_a = faro_core_1.faro.api) === null || _a === void 0 ? void 0 : _a.setSession(newSession.sessionMeta);
57
- (_b = sessionTrackingConfig === null || sessionTrackingConfig === void 0 ? void 0 : sessionTrackingConfig.onSessionChange) === null || _b === void 0 ? void 0 : _b.call(sessionTrackingConfig, (_c = sessionFromStorage === null || sessionFromStorage === void 0 ? void 0 : sessionFromStorage.sessionMeta) !== null && _c !== void 0 ? _c : null, newSession.sessionMeta);
63
+ (_b = faro_core_1.faro.api) === null || _b === void 0 ? void 0 : _b.setSession(newSession.sessionMeta);
64
+ (_c = sessionTrackingConfig === null || sessionTrackingConfig === void 0 ? void 0 : sessionTrackingConfig.onSessionChange) === null || _c === void 0 ? void 0 : _c.call(sessionTrackingConfig, (_d = sessionFromStorage === null || sessionFromStorage === void 0 ? void 0 : sessionFromStorage.sessionMeta) !== null && _d !== void 0 ? _d : null, newSession.sessionMeta);
58
65
  }
59
66
  };
60
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sessionManagerUtils.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionManagerUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { dateNow, deepEqual, EVENT_OVERRIDES_SERVICE_NAME, faro, genShortID, isEmpty } from '@grafana/faro-core';\nimport type { Meta, MetaOverrides } from '@grafana/faro-core';\n\nimport { isLocalStorageAvailable, isSessionStorageAvailable } from '../../../utils';\n\nimport { isSampled } from './sampling';\nimport { SESSION_EXPIRATION_TIME, SESSION_INACTIVITY_TIME } from './sessionConstants';\nimport type { FaroUserSession } from './types';\n\ntype CreateUserSessionObjectParams = {\n sessionId?: string;\n started?: number;\n lastActivity?: number;\n isSampled?: boolean;\n};\n\nexport function createUserSessionObject({\n sessionId,\n started,\n lastActivity,\n isSampled = true,\n}: CreateUserSessionObjectParams = {}): FaroUserSession {\n const now = dateNow();\n\n const generateSessionId = faro.config?.sessionTracking?.generateSessionId;\n\n if (sessionId == null) {\n sessionId = typeof generateSessionId === 'function' ? generateSessionId() : genShortID();\n }\n\n return {\n sessionId,\n lastActivity: lastActivity ?? now,\n started: started ?? now,\n isSampled: isSampled,\n };\n}\n\nexport function isUserSessionValid(session: FaroUserSession | null): boolean {\n if (session == null) {\n return false;\n }\n\n const now = dateNow();\n const lifetimeValid = now - session.started < SESSION_EXPIRATION_TIME;\n\n if (!lifetimeValid) {\n return false;\n }\n\n const inactivityPeriodValid = now - session.lastActivity < SESSION_INACTIVITY_TIME;\n return inactivityPeriodValid;\n}\n\ntype GetUserSessionUpdaterParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n};\n\ntype UpdateSessionParams = { forceSessionExtend: boolean };\n\nexport function getUserSessionUpdater({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionUpdaterParams): (options?: UpdateSessionParams) => void {\n return function updateSession({ forceSessionExtend } = { forceSessionExtend: false }): void {\n if (!fetchUserSession || !storeUserSession) {\n return;\n }\n\n const sessionTrackingConfig = faro.config.sessionTracking;\n const isPersistentSessions = sessionTrackingConfig?.persistent;\n\n if ((isPersistentSessions && !isLocalStorageAvailable) || (!isPersistentSessions && !isSessionStorageAvailable)) {\n return;\n }\n\n const sessionFromStorage = fetchUserSession();\n\n if (forceSessionExtend === false && isUserSessionValid(sessionFromStorage)) {\n storeUserSession({ ...sessionFromStorage!, lastActivity: dateNow() });\n } else {\n let newSession = addSessionMetadataToNextSession(\n createUserSessionObject({ isSampled: isSampled() }),\n sessionFromStorage\n );\n\n storeUserSession(newSession);\n\n faro.api?.setSession(newSession.sessionMeta);\n sessionTrackingConfig?.onSessionChange?.(sessionFromStorage?.sessionMeta ?? null, newSession.sessionMeta!);\n }\n };\n}\n\nexport function addSessionMetadataToNextSession(newSession: FaroUserSession, previousSession: FaroUserSession | null) {\n const sessionWithMeta: Required<FaroUserSession> = {\n ...newSession,\n sessionMeta: {\n id: newSession.sessionId,\n attributes: removeUndefinedValues({\n ...faro.config.sessionTracking?.session?.attributes,\n ...(faro.metas.value.session?.attributes ?? {}),\n isSampled: newSession.isSampled.toString(),\n }),\n },\n };\n\n const overrides = faro.metas.value.session?.overrides ?? previousSession?.sessionMeta?.overrides;\n if (!isEmpty(overrides)) {\n sessionWithMeta.sessionMeta.overrides = overrides;\n }\n\n const previousSessionId = previousSession?.sessionId;\n if (previousSessionId != null) {\n sessionWithMeta.sessionMeta.attributes!['previousSession'] = previousSessionId;\n }\n\n return sessionWithMeta;\n}\n\ntype GetUserSessionMetaUpdateHandlerParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n};\n\nexport function getSessionMetaUpdateHandler({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionMetaUpdateHandlerParams) {\n let isSyncing = false;\n\n return function syncSessionIfChangedExternally(meta: Meta) {\n if (isSyncing) {\n return;\n }\n const session = meta.session;\n const sessionFromSessionStorage = fetchUserSession();\n\n let sessionId = session?.id;\n const sessionAttributes = session?.attributes;\n const sessionOverrides = session?.overrides;\n\n const storedSessionMeta = sessionFromSessionStorage?.sessionMeta;\n const storedSessionMetaOverrides = storedSessionMeta?.overrides;\n\n const hasSessionOverridesChanged = !!sessionOverrides && !deepEqual(sessionOverrides, storedSessionMetaOverrides);\n const hasAttributesChanged = !!sessionAttributes && !deepEqual(sessionAttributes, storedSessionMeta?.attributes);\n const hasSessionIdChanged = !!session && sessionId !== sessionFromSessionStorage?.sessionId;\n\n if (hasSessionIdChanged || hasAttributesChanged || hasSessionOverridesChanged) {\n const userSession = addSessionMetadataToNextSession(\n createUserSessionObject({ sessionId, isSampled: isSampled() }),\n sessionFromSessionStorage\n );\n\n storeUserSession(userSession);\n sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);\n\n isSyncing = true;\n try {\n faro.api.setSession(userSession.sessionMeta);\n } finally {\n isSyncing = false;\n }\n }\n };\n}\n\nfunction removeUndefinedValues(obj: Record<string, string | undefined>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (value !== undefined) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction sendOverrideEvent(\n hasSessionOverridesChanged: boolean,\n sessionOverrides: MetaOverrides = {},\n storedSessionOverrides: MetaOverrides = {}\n) {\n if (!hasSessionOverridesChanged) {\n return;\n }\n\n const serviceName = sessionOverrides.serviceName;\n const previousServiceName = storedSessionOverrides.serviceName ?? faro.metas.value.app?.name ?? '';\n\n if (serviceName && serviceName !== previousServiceName) {\n faro.api.pushEvent(EVENT_OVERRIDES_SERVICE_NAME, {\n serviceName,\n previousServiceName,\n });\n }\n}\n"]}
1
+ {"version":3,"file":"sessionManagerUtils.js","sourceRoot":"","sources":["../../../../../src/instrumentations/session/sessionManager/sessionManagerUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import { dateNow, deepEqual, EVENT_OVERRIDES_SERVICE_NAME, faro, genShortID, isEmpty } from '@grafana/faro-core';\nimport type { Meta, MetaOverrides } from '@grafana/faro-core';\n\nimport { isLocalStorageAvailable, isSessionStorageAvailable } from '../../../utils';\n\nimport { isSampled } from './sampling';\nimport { SESSION_EXPIRATION_TIME, SESSION_INACTIVITY_TIME } from './sessionConstants';\nimport type { FaroUserSession } from './types';\n\ntype CreateUserSessionObjectParams = {\n sessionId?: string;\n started?: number;\n lastActivity?: number;\n isSampled?: boolean;\n};\n\nexport function createUserSessionObject({\n sessionId,\n started,\n lastActivity,\n isSampled = true,\n}: CreateUserSessionObjectParams = {}): FaroUserSession {\n const now = dateNow();\n\n const generateSessionId = faro.config?.sessionTracking?.generateSessionId;\n\n if (sessionId == null) {\n sessionId = typeof generateSessionId === 'function' ? generateSessionId() : genShortID();\n }\n\n return {\n sessionId,\n lastActivity: lastActivity ?? now,\n started: started ?? now,\n isSampled: isSampled,\n };\n}\n\nexport function isUserSessionValid(session: FaroUserSession | null): boolean {\n if (session == null) {\n return false;\n }\n\n const now = dateNow();\n const lifetimeValid = now - session.started < SESSION_EXPIRATION_TIME;\n\n if (!lifetimeValid) {\n return false;\n }\n\n const inactivityPeriodValid = now - session.lastActivity < SESSION_INACTIVITY_TIME;\n return inactivityPeriodValid;\n}\n\ntype GetUserSessionUpdaterParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n // Silently adopt another tab's session into in-memory metas (cross-tab sync).\n // Optional: only the valid (non-force-extend) branch uses it.\n adoptSession?: (sessionMeta: NonNullable<FaroUserSession['sessionMeta']>) => void;\n};\n\ntype UpdateSessionParams = { forceSessionExtend: boolean };\n\nexport function getUserSessionUpdater({\n fetchUserSession,\n storeUserSession,\n adoptSession,\n}: GetUserSessionUpdaterParams): (options?: UpdateSessionParams) => void {\n return function updateSession({ forceSessionExtend } = { forceSessionExtend: false }): void {\n if (!fetchUserSession || !storeUserSession) {\n return;\n }\n\n const sessionTrackingConfig = faro.config.sessionTracking;\n const isPersistentSessions = sessionTrackingConfig?.persistent;\n\n if ((isPersistentSessions && !isLocalStorageAvailable) || (!isPersistentSessions && !isSessionStorageAvailable)) {\n return;\n }\n\n const sessionFromStorage = fetchUserSession();\n\n if (forceSessionExtend === false && isUserSessionValid(sessionFromStorage)) {\n storeUserSession({ ...sessionFromStorage!, lastActivity: dateNow() });\n\n // Another tab rotated the shared session; adopt it so we stop emitting the stale id.\n const inMemorySessionId = faro.metas.value.session?.id;\n if (\n adoptSession != null &&\n sessionFromStorage!.sessionMeta != null &&\n sessionFromStorage!.sessionId !== inMemorySessionId\n ) {\n adoptSession(sessionFromStorage!.sessionMeta);\n }\n } else {\n let newSession = addSessionMetadataToNextSession(\n createUserSessionObject({ isSampled: isSampled() }),\n sessionFromStorage\n );\n\n storeUserSession(newSession);\n\n faro.api?.setSession(newSession.sessionMeta);\n sessionTrackingConfig?.onSessionChange?.(sessionFromStorage?.sessionMeta ?? null, newSession.sessionMeta!);\n }\n };\n}\n\nexport function addSessionMetadataToNextSession(newSession: FaroUserSession, previousSession: FaroUserSession | null) {\n const sessionWithMeta: Required<FaroUserSession> = {\n ...newSession,\n sessionMeta: {\n id: newSession.sessionId,\n attributes: removeUndefinedValues({\n ...faro.config.sessionTracking?.session?.attributes,\n ...(faro.metas.value.session?.attributes ?? {}),\n isSampled: newSession.isSampled.toString(),\n }),\n },\n };\n\n const overrides = faro.metas.value.session?.overrides ?? previousSession?.sessionMeta?.overrides;\n if (!isEmpty(overrides)) {\n sessionWithMeta.sessionMeta.overrides = overrides;\n }\n\n const previousSessionId = previousSession?.sessionId;\n if (previousSessionId != null) {\n sessionWithMeta.sessionMeta.attributes!['previousSession'] = previousSessionId;\n }\n\n return sessionWithMeta;\n}\n\ntype GetUserSessionMetaUpdateHandlerParams = {\n storeUserSession: (session: FaroUserSession) => void;\n fetchUserSession: () => FaroUserSession | null;\n};\n\nexport function getSessionMetaUpdateHandler({\n fetchUserSession,\n storeUserSession,\n}: GetUserSessionMetaUpdateHandlerParams) {\n let isSyncing = false;\n\n return function syncSessionIfChangedExternally(meta: Meta) {\n if (isSyncing) {\n return;\n }\n const session = meta.session;\n const sessionFromSessionStorage = fetchUserSession();\n\n let sessionId = session?.id;\n const sessionAttributes = session?.attributes;\n const sessionOverrides = session?.overrides;\n\n const storedSessionMeta = sessionFromSessionStorage?.sessionMeta;\n const storedSessionMetaOverrides = storedSessionMeta?.overrides;\n\n const hasSessionOverridesChanged = !!sessionOverrides && !deepEqual(sessionOverrides, storedSessionMetaOverrides);\n const hasAttributesChanged = !!sessionAttributes && !deepEqual(sessionAttributes, storedSessionMeta?.attributes);\n const hasSessionIdChanged = !!session && sessionId !== sessionFromSessionStorage?.sessionId;\n\n if (hasSessionIdChanged || hasAttributesChanged || hasSessionOverridesChanged) {\n const userSession = addSessionMetadataToNextSession(\n createUserSessionObject({ sessionId, isSampled: isSampled() }),\n sessionFromSessionStorage\n );\n\n storeUserSession(userSession);\n sendOverrideEvent(hasSessionOverridesChanged, sessionOverrides, storedSessionMetaOverrides);\n\n isSyncing = true;\n try {\n faro.api.setSession(userSession.sessionMeta);\n } finally {\n isSyncing = false;\n }\n }\n };\n}\n\nfunction removeUndefinedValues(obj: Record<string, string | undefined>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const key of Object.keys(obj)) {\n const value = obj[key];\n if (value !== undefined) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction sendOverrideEvent(\n hasSessionOverridesChanged: boolean,\n sessionOverrides: MetaOverrides = {},\n storedSessionOverrides: MetaOverrides = {}\n) {\n if (!hasSessionOverridesChanged) {\n return;\n }\n\n const serviceName = sessionOverrides.serviceName;\n const previousServiceName = storedSessionOverrides.serviceName ?? faro.metas.value.app?.name ?? '';\n\n if (serviceName && serviceName !== previousServiceName) {\n faro.api.pushEvent(EVENT_OVERRIDES_SERVICE_NAME, {\n serviceName,\n previousServiceName,\n });\n }\n}\n"]}
@@ -28,8 +28,11 @@ const DEFAULT_BUFFER_SIZE = 30;
28
28
  const DEFAULT_CONCURRENCY = 5; // chrome supports 10 total, firefox 17
29
29
  const DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;
30
30
  const BEACON_BODY_SIZE_LIMIT = 60000;
31
+ const MAX_KEEPALIVE_REQUESTS = 9;
31
32
  const TOO_MANY_REQUESTS = 429;
32
33
  const ACCEPTED = 202;
34
+ let pendingKeepaliveBodySize = 0;
35
+ let pendingKeepaliveRequests = 0;
33
36
  class FetchTransport extends faro_core_1.BaseTransport {
34
37
  constructor(options) {
35
38
  var _a, _b, _c, _d, _e;
@@ -64,6 +67,7 @@ class FetchTransport extends faro_core_1.BaseTransport {
64
67
  const jsonBody = JSON.stringify((0, faro_core_1.getTransportBody)(items));
65
68
  const { url, requestOptions, apiKey } = this.options;
66
69
  const _a = requestOptions !== null && requestOptions !== void 0 ? requestOptions : {}, { headers = {} } = _a, restOfRequestOptions = __rest(_a, ["headers"]);
70
+ const { keepalive: configuredKeepalive } = restOfRequestOptions, requestOptionsWithoutKeepalive = __rest(restOfRequestOptions, ["keepalive"]);
67
71
  let sessionId;
68
72
  const sessionMeta = this.metas.value.session;
69
73
  if (sessionMeta != null) {
@@ -81,23 +85,8 @@ class FetchTransport extends faro_core_1.BaseTransport {
81
85
  bodySize = body.size;
82
86
  compressionHeaders['Content-Encoding'] = 'gzip';
83
87
  }
84
- return fetch(url, Object.assign({ method: 'POST', headers: Object.assign(Object.assign(Object.assign(Object.assign({ 'Content-Type': 'application/json' }, compressionHeaders), resolvedHeaders), (apiKey ? { 'x-api-key': apiKey } : {})), (sessionId ? { 'x-faro-session-id': sessionId } : {})), body, keepalive: bodySize <= BEACON_BODY_SIZE_LIMIT }, (restOfRequestOptions !== null && restOfRequestOptions !== void 0 ? restOfRequestOptions : {})))
85
- .then((response) => __awaiter(this, void 0, void 0, function* () {
86
- if (response.status === ACCEPTED) {
87
- const sessionExpired = response.headers.get('X-Faro-Session-Status') === 'invalid';
88
- if (sessionExpired) {
89
- this.extendFaroSession(this.config, this.logDebug);
90
- }
91
- }
92
- if (response.status === TOO_MANY_REQUESTS) {
93
- this.disabledUntil = this.getRetryAfterDate(response);
94
- this.logWarn(`Too many requests, backing off until ${this.disabledUntil}`);
95
- }
96
- // read the body so the connection can be closed
97
- response.text().catch(faro_core_1.noop);
98
- return response;
99
- }))
100
- .catch((err) => {
88
+ const requestInit = Object.assign({ method: 'POST', headers: Object.assign(Object.assign(Object.assign(Object.assign({ 'Content-Type': 'application/json' }, compressionHeaders), resolvedHeaders), (apiKey ? { 'x-api-key': apiKey } : {})), (sessionId ? { 'x-faro-session-id': sessionId } : {})), body }, (requestOptionsWithoutKeepalive !== null && requestOptionsWithoutKeepalive !== void 0 ? requestOptionsWithoutKeepalive : {}));
89
+ return this.fetchWithKeepaliveRetry(url, requestInit, bodySize, configuredKeepalive).catch((err) => {
101
90
  this.logError('Failed sending payload to the receiver\n', JSON.parse(jsonBody), err);
102
91
  });
103
92
  }));
@@ -129,6 +118,77 @@ class FetchTransport extends faro_core_1.BaseTransport {
129
118
  }
130
119
  return new Date(now + this.rateLimitBackoffMs);
131
120
  }
121
+ reserveKeepalive(bodySize, configuredKeepalive) {
122
+ if (configuredKeepalive === false) {
123
+ return {
124
+ keepalive: false,
125
+ release: faro_core_1.noop,
126
+ };
127
+ }
128
+ if (bodySize > BEACON_BODY_SIZE_LIMIT ||
129
+ pendingKeepaliveBodySize + bodySize > BEACON_BODY_SIZE_LIMIT ||
130
+ pendingKeepaliveRequests >= MAX_KEEPALIVE_REQUESTS) {
131
+ this.logDebug('Disabling keepalive because the pending keepalive request budget would be exceeded.');
132
+ return {
133
+ keepalive: false,
134
+ release: faro_core_1.noop,
135
+ };
136
+ }
137
+ pendingKeepaliveBodySize += bodySize;
138
+ pendingKeepaliveRequests++;
139
+ let released = false;
140
+ return {
141
+ keepalive: true,
142
+ release: () => {
143
+ if (released) {
144
+ return;
145
+ }
146
+ released = true;
147
+ pendingKeepaliveBodySize = Math.max(0, pendingKeepaliveBodySize - bodySize);
148
+ pendingKeepaliveRequests = Math.max(0, pendingKeepaliveRequests - 1);
149
+ },
150
+ };
151
+ }
152
+ fetchWithKeepaliveRetry(url, requestInit, bodySize, configuredKeepalive) {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ const keepaliveReservation = this.reserveKeepalive(bodySize, configuredKeepalive);
155
+ try {
156
+ const response = yield fetch(url, Object.assign(Object.assign({}, requestInit), { keepalive: keepaliveReservation.keepalive }));
157
+ return this.handleResponse(response);
158
+ }
159
+ catch (err) {
160
+ if (keepaliveReservation.keepalive && this.isFetchNetworkError(err)) {
161
+ this.logDebug('Retrying failed keepalive request with keepalive disabled.');
162
+ const response = yield fetch(url, Object.assign(Object.assign({}, requestInit), { keepalive: false }));
163
+ return this.handleResponse(response);
164
+ }
165
+ throw err;
166
+ }
167
+ finally {
168
+ keepaliveReservation.release();
169
+ }
170
+ });
171
+ }
172
+ handleResponse(response) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ if (response.status === ACCEPTED) {
175
+ const sessionExpired = response.headers.get('X-Faro-Session-Status') === 'invalid';
176
+ if (sessionExpired) {
177
+ this.extendFaroSession(this.config, this.logDebug);
178
+ }
179
+ }
180
+ if (response.status === TOO_MANY_REQUESTS) {
181
+ this.disabledUntil = this.getRetryAfterDate(response);
182
+ this.logWarn(`Too many requests, backing off until ${this.disabledUntil}`);
183
+ }
184
+ // read the body so the connection can be closed
185
+ response.text().catch(faro_core_1.noop);
186
+ return response;
187
+ });
188
+ }
189
+ isFetchNetworkError(err) {
190
+ return err instanceof TypeError;
191
+ }
132
192
  compress(body) {
133
193
  return __awaiter(this, void 0, void 0, function* () {
134
194
  const stream = new ReadableStream({
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/fetch/transport.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAAwH;AAGxH,kFAA0F;AAC1F,2GAA0G;AAI1G,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAC/B,MAAM,mBAAmB,GAAG,CAAC,CAAC,CAAC,uCAAuC;AACtE,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAE3C,MAAM,sBAAsB,GAAG,KAAK,CAAC;AACrC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErB,MAAa,cAAe,SAAQ,yBAAa;IAW/C,YAAoB,OAA8B;;QAChD,KAAK,EAAE,CAAC;QADU,YAAO,GAAP,OAAO,CAAuB;QAVzC,SAAI,GAAG,uCAAuC,CAAC;QAC/C,YAAO,GAAG,mBAAO,CAAC;QAOnB,kBAAa,GAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QAKxC,IAAI,CAAC,kBAAkB,GAAG,MAAA,OAAO,CAAC,yBAAyB,mCAAI,6BAA6B,CAAC;QAC7F,IAAI,CAAC,MAAM,GAAG,MAAA,OAAO,CAAC,MAAM,mCAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAEnD,MAAM,kBAAkB,GAAG,MAAA,OAAO,CAAC,kBAAkB,mCAAI,KAAK,CAAC;QAE/D,IAAI,kBAAkB,IAAI,OAAO,iBAAiB,KAAK,WAAW,EAAE,CAAC;YACnE,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;YAChC,IAAI,CAAC,OAAO,CACV,qGAAqG,CACtG,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,IAAA,+BAAmB,EAAC;YACvC,IAAI,EAAE,MAAA,OAAO,CAAC,UAAU,mCAAI,mBAAmB;YAC/C,WAAW,EAAE,MAAA,OAAO,CAAC,WAAW,mCAAI,mBAAmB;SACxD,CAAC,CAAC;IACL,CAAC;IAEK,IAAI,CAAC,KAAsB;;YAC/B,IAAI,CAAC;gBACH,IAAI,IAAI,CAAC,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;oBACjD,IAAI,CAAC,OAAO,CAAC,mEAAmE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;oBAEtG,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;gBAC3B,CAAC;gBAED,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAS,EAAE;oBACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAA,4BAAgB,EAAC,KAAK,CAAC,CAAC,CAAC;oBAEzD,MAAM,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;oBAErD,MAAM,KAA4C,cAAc,aAAd,cAAc,cAAd,cAAc,GAAI,EAAE,EAAhE,EAAE,OAAO,GAAG,EAAE,OAAkD,EAA7C,oBAAoB,cAAvC,WAAyC,CAAuB,CAAC;oBAEvE,IAAI,SAAS,CAAC;oBACd,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC;oBAC7C,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;wBACxB,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC;oBAC7B,CAAC;oBAED,MAAM,eAAe,GAA2B,EAAE,CAAC;oBACnD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;wBACnD,eAAe,CAAC,GAAG,CAAC,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;oBAC9F,CAAC;oBAED,IAAI,IAAI,GAAkB,QAAQ,CAAC;oBACnC,IAAI,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC;oBAC/B,MAAM,kBAAkB,GAA2B,EAAE,CAAC;oBAEtD,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;wBAC5B,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;wBACrC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;wBACrB,kBAAkB,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC;oBAClD,CAAC;oBAED,OAAO,KAAK,CAAC,GAAG,kBACd,MAAM,EAAE,MAAM,EACd,OAAO,4DACL,cAAc,EAAE,kBAAkB,IAC/B,kBAAkB,GAClB,eAAe,GACf,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GACvC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAE1D,IAAI,EACJ,SAAS,EAAE,QAAQ,IAAI,sBAAsB,IAC1C,CAAC,oBAAoB,aAApB,oBAAoB,cAApB,oBAAoB,GAAI,EAAE,CAAC,EAC/B;yBACC,IAAI,CAAC,CAAO,QAAQ,EAAE,EAAE;wBACvB,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;4BACjC,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,KAAK,SAAS,CAAC;4BAEnF,IAAI,cAAc,EAAE,CAAC;gCACnB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;4BACrD,CAAC;wBACH,CAAC;wBAED,IAAI,QAAQ,CAAC,MAAM,KAAK,iBAAiB,EAAE,CAAC;4BAC1C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;4BACtD,IAAI,CAAC,OAAO,CAAC,wCAAwC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;wBAC7E,CAAC;wBAED,gDAAgD;wBAChD,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,gBAAI,CAAC,CAAC;wBAC5B,OAAO,QAAQ,CAAC;oBAClB,CAAC,CAAA,CAAC;yBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;wBACb,IAAI,CAAC,QAAQ,CAAC,0CAA0C,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;oBACvF,CAAC,CAAC,CAAC;gBACP,CAAC,CAAA,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;KAAA;IAEQ,aAAa;;QACpB,OAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAc,CAAC,MAAM,CAAC,MAAA,IAAI,CAAC,MAAM,CAAC,UAAU,mCAAI,EAAE,CAAC,CAAC;IAC/E,CAAC;IAEQ,SAAS;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,iBAAiB,CAAC,QAAkB;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAE7D,IAAI,gBAAgB,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;YAEvC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClB,OAAO,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC;YACtC,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;YAE1C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjB,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACjD,CAAC;IAEa,QAAQ,CAAC,IAAY;;YACjC,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;gBAChC,KAAK,CAAC,UAAU;oBACd,UAAU,CAAC,OAAO,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;oBACnD,UAAU,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;aACF,CAAC,CAAC,WAAW,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;YAE9C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YAClC,MAAM,MAAM,GAAe,EAAE,CAAC;YAC9B,SAAS,CAAC;gBACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI,EAAE,CAAC;oBACT,MAAM;gBACR,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YACD,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;KAAA;IAEO,iBAAiB,CAAC,MAAc,EAAE,QAAmC;QAC3E,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;QAE/C,MAAM,qBAAqB,GAAG,MAAM,CAAC,eAAe,CAAC;QAErD,IAAI,qBAAqB,aAArB,qBAAqB,uBAArB,qBAAqB,CAAE,OAAO,EAAE,CAAC;YACnC,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAAG,IAAA,0CAAyB,EAAC,qBAAqB,CAAC,CAAC;YAEhG,IAAA,2CAAqB,EAAC,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;YAE5F,QAAQ,CAAC,GAAG,oBAAoB,uBAAuB,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,GAAG,oBAAoB,GAAG,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;CACF;AA9KD,wCA8KC","sourcesContent":["import { BaseExtension, BaseTransport, createPromiseBuffer, getTransportBody, noop, VERSION } from '@grafana/faro-core';\nimport type { Config, Patterns, PromiseBuffer, TransportItem } from '@grafana/faro-core';\n\nimport { getSessionManagerByConfig } from '../../instrumentations/session/sessionManager';\nimport { getUserSessionUpdater } from '../../instrumentations/session/sessionManager/sessionManagerUtils';\n\nimport type { FetchTransportOptions } from './types';\n\nconst DEFAULT_BUFFER_SIZE = 30;\nconst DEFAULT_CONCURRENCY = 5; // chrome supports 10 total, firefox 17\nconst DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;\n\nconst BEACON_BODY_SIZE_LIMIT = 60000;\nconst TOO_MANY_REQUESTS = 429;\nconst ACCEPTED = 202;\n\nexport class FetchTransport extends BaseTransport {\n readonly name = '@grafana/faro-web-sdk:transport-fetch';\n readonly version = VERSION;\n\n promiseBuffer: PromiseBuffer<Response | void>;\n\n private readonly rateLimitBackoffMs: number;\n private readonly getNow: () => number;\n private readonly compressionEnabled: boolean;\n private disabledUntil: Date = new Date(0);\n\n constructor(private options: FetchTransportOptions) {\n super();\n\n this.rateLimitBackoffMs = options.defaultRateLimitBackoffMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS;\n this.getNow = options.getNow ?? (() => Date.now());\n\n const requestCompression = options.requestCompression ?? false;\n\n if (requestCompression && typeof CompressionStream === 'undefined') {\n this.compressionEnabled = false;\n this.logWarn(\n 'requestCompression is enabled but CompressionStream is not available. Falling back to uncompressed.'\n );\n } else {\n this.compressionEnabled = requestCompression;\n }\n\n this.promiseBuffer = createPromiseBuffer({\n size: options.bufferSize ?? DEFAULT_BUFFER_SIZE,\n concurrency: options.concurrency ?? DEFAULT_CONCURRENCY,\n });\n }\n\n async send(items: TransportItem[]): Promise<void> {\n try {\n if (this.disabledUntil > new Date(this.getNow())) {\n this.logWarn(`Dropping transport item due to too many requests. Backoff until ${this.disabledUntil}`);\n\n return Promise.resolve();\n }\n\n await this.promiseBuffer.add(async () => {\n const jsonBody = JSON.stringify(getTransportBody(items));\n\n const { url, requestOptions, apiKey } = this.options;\n\n const { headers = {}, ...restOfRequestOptions } = requestOptions ?? {};\n\n let sessionId;\n const sessionMeta = this.metas.value.session;\n if (sessionMeta != null) {\n sessionId = sessionMeta.id;\n }\n\n const resolvedHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n resolvedHeaders[key] = typeof value === 'function' ? await Promise.resolve(value()) : value;\n }\n\n let body: string | Blob = jsonBody;\n let bodySize = jsonBody.length;\n const compressionHeaders: Record<string, string> = {};\n\n if (this.compressionEnabled) {\n body = await this.compress(jsonBody);\n bodySize = body.size;\n compressionHeaders['Content-Encoding'] = 'gzip';\n }\n\n return fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...compressionHeaders,\n ...resolvedHeaders,\n ...(apiKey ? { 'x-api-key': apiKey } : {}),\n ...(sessionId ? { 'x-faro-session-id': sessionId } : {}),\n },\n body,\n keepalive: bodySize <= BEACON_BODY_SIZE_LIMIT,\n ...(restOfRequestOptions ?? {}),\n })\n .then(async (response) => {\n if (response.status === ACCEPTED) {\n const sessionExpired = response.headers.get('X-Faro-Session-Status') === 'invalid';\n\n if (sessionExpired) {\n this.extendFaroSession(this.config, this.logDebug);\n }\n }\n\n if (response.status === TOO_MANY_REQUESTS) {\n this.disabledUntil = this.getRetryAfterDate(response);\n this.logWarn(`Too many requests, backing off until ${this.disabledUntil}`);\n }\n\n // read the body so the connection can be closed\n response.text().catch(noop);\n return response;\n })\n .catch((err) => {\n this.logError('Failed sending payload to the receiver\\n', JSON.parse(jsonBody), err);\n });\n });\n } catch (err) {\n this.logError(err);\n }\n }\n\n override getIgnoreUrls(): Patterns {\n return ([this.options.url] as Patterns).concat(this.config.ignoreUrls ?? []);\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n private getRetryAfterDate(response: Response): Date {\n const now = this.getNow();\n const retryAfterHeader = response.headers.get('Retry-After');\n\n if (retryAfterHeader) {\n const delay = Number(retryAfterHeader);\n\n if (!isNaN(delay)) {\n return new Date(delay * 1000 + now);\n }\n\n const date = Date.parse(retryAfterHeader);\n\n if (!isNaN(date)) {\n return new Date(date);\n }\n }\n\n return new Date(now + this.rateLimitBackoffMs);\n }\n\n private async compress(body: string): Promise<Blob> {\n const stream = new ReadableStream({\n start(controller) {\n controller.enqueue(new TextEncoder().encode(body));\n controller.close();\n },\n }).pipeThrough(new CompressionStream('gzip'));\n\n const reader = stream.getReader();\n const chunks: BlobPart[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n chunks.push(value);\n }\n return new Blob(chunks);\n }\n\n private extendFaroSession(config: Config, logDebug: BaseExtension['logDebug']) {\n const SessionExpiredString = `Session expired`;\n\n const sessionTrackingConfig = config.sessionTracking;\n\n if (sessionTrackingConfig?.enabled) {\n const { fetchUserSession, storeUserSession } = getSessionManagerByConfig(sessionTrackingConfig);\n\n getUserSessionUpdater({ fetchUserSession, storeUserSession })({ forceSessionExtend: true });\n\n logDebug(`${SessionExpiredString} created new session.`);\n } else {\n logDebug(`${SessionExpiredString}.`);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../../../src/transports/fetch/transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseExtension, BaseTransport, createPromiseBuffer, getTransportBody, noop, VERSION } from '@grafana/faro-core';\nimport type { Config, Patterns, PromiseBuffer, TransportItem } from '@grafana/faro-core';\n\nimport { getSessionManagerByConfig } from '../../instrumentations/session/sessionManager';\nimport { getUserSessionUpdater } from '../../instrumentations/session/sessionManager/sessionManagerUtils';\n\nimport type { FetchTransportOptions } from './types';\n\nconst DEFAULT_BUFFER_SIZE = 30;\nconst DEFAULT_CONCURRENCY = 5; // chrome supports 10 total, firefox 17\nconst DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;\n\nconst BEACON_BODY_SIZE_LIMIT = 60000;\nconst MAX_KEEPALIVE_REQUESTS = 9;\nconst TOO_MANY_REQUESTS = 429;\nconst ACCEPTED = 202;\n\nlet pendingKeepaliveBodySize = 0;\nlet pendingKeepaliveRequests = 0;\n\ninterface KeepaliveReservation {\n keepalive: boolean;\n release: () => void;\n}\n\nexport class FetchTransport extends BaseTransport {\n readonly name = '@grafana/faro-web-sdk:transport-fetch';\n readonly version = VERSION;\n\n promiseBuffer: PromiseBuffer<Response | void>;\n\n private readonly rateLimitBackoffMs: number;\n private readonly getNow: () => number;\n private readonly compressionEnabled: boolean;\n private disabledUntil: Date = new Date(0);\n\n constructor(private options: FetchTransportOptions) {\n super();\n\n this.rateLimitBackoffMs = options.defaultRateLimitBackoffMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS;\n this.getNow = options.getNow ?? (() => Date.now());\n\n const requestCompression = options.requestCompression ?? false;\n\n if (requestCompression && typeof CompressionStream === 'undefined') {\n this.compressionEnabled = false;\n this.logWarn(\n 'requestCompression is enabled but CompressionStream is not available. Falling back to uncompressed.'\n );\n } else {\n this.compressionEnabled = requestCompression;\n }\n\n this.promiseBuffer = createPromiseBuffer({\n size: options.bufferSize ?? DEFAULT_BUFFER_SIZE,\n concurrency: options.concurrency ?? DEFAULT_CONCURRENCY,\n });\n }\n\n async send(items: TransportItem[]): Promise<void> {\n try {\n if (this.disabledUntil > new Date(this.getNow())) {\n this.logWarn(`Dropping transport item due to too many requests. Backoff until ${this.disabledUntil}`);\n\n return Promise.resolve();\n }\n\n await this.promiseBuffer.add(async () => {\n const jsonBody = JSON.stringify(getTransportBody(items));\n\n const { url, requestOptions, apiKey } = this.options;\n\n const { headers = {}, ...restOfRequestOptions } = requestOptions ?? {};\n const { keepalive: configuredKeepalive, ...requestOptionsWithoutKeepalive } = restOfRequestOptions;\n\n let sessionId;\n const sessionMeta = this.metas.value.session;\n if (sessionMeta != null) {\n sessionId = sessionMeta.id;\n }\n\n const resolvedHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n resolvedHeaders[key] = typeof value === 'function' ? await Promise.resolve(value()) : value;\n }\n\n let body: string | Blob = jsonBody;\n let bodySize = jsonBody.length;\n const compressionHeaders: Record<string, string> = {};\n\n if (this.compressionEnabled) {\n body = await this.compress(jsonBody);\n bodySize = body.size;\n compressionHeaders['Content-Encoding'] = 'gzip';\n }\n\n const requestInit: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...compressionHeaders,\n ...resolvedHeaders,\n ...(apiKey ? { 'x-api-key': apiKey } : {}),\n ...(sessionId ? { 'x-faro-session-id': sessionId } : {}),\n },\n body,\n ...(requestOptionsWithoutKeepalive ?? {}),\n };\n\n return this.fetchWithKeepaliveRetry(url, requestInit, bodySize, configuredKeepalive).catch((err) => {\n this.logError('Failed sending payload to the receiver\\n', JSON.parse(jsonBody), err);\n });\n });\n } catch (err) {\n this.logError(err);\n }\n }\n\n override getIgnoreUrls(): Patterns {\n return ([this.options.url] as Patterns).concat(this.config.ignoreUrls ?? []);\n }\n\n override isBatched(): boolean {\n return true;\n }\n\n private getRetryAfterDate(response: Response): Date {\n const now = this.getNow();\n const retryAfterHeader = response.headers.get('Retry-After');\n\n if (retryAfterHeader) {\n const delay = Number(retryAfterHeader);\n\n if (!isNaN(delay)) {\n return new Date(delay * 1000 + now);\n }\n\n const date = Date.parse(retryAfterHeader);\n\n if (!isNaN(date)) {\n return new Date(date);\n }\n }\n\n return new Date(now + this.rateLimitBackoffMs);\n }\n\n private reserveKeepalive(bodySize: number, configuredKeepalive?: boolean): KeepaliveReservation {\n if (configuredKeepalive === false) {\n return {\n keepalive: false,\n release: noop,\n };\n }\n\n if (\n bodySize > BEACON_BODY_SIZE_LIMIT ||\n pendingKeepaliveBodySize + bodySize > BEACON_BODY_SIZE_LIMIT ||\n pendingKeepaliveRequests >= MAX_KEEPALIVE_REQUESTS\n ) {\n this.logDebug('Disabling keepalive because the pending keepalive request budget would be exceeded.');\n\n return {\n keepalive: false,\n release: noop,\n };\n }\n\n pendingKeepaliveBodySize += bodySize;\n pendingKeepaliveRequests++;\n\n let released = false;\n\n return {\n keepalive: true,\n release: () => {\n if (released) {\n return;\n }\n\n released = true;\n pendingKeepaliveBodySize = Math.max(0, pendingKeepaliveBodySize - bodySize);\n pendingKeepaliveRequests = Math.max(0, pendingKeepaliveRequests - 1);\n },\n };\n }\n\n private async fetchWithKeepaliveRetry(\n url: string,\n requestInit: RequestInit,\n bodySize: number,\n configuredKeepalive?: boolean\n ): Promise<Response> {\n const keepaliveReservation = this.reserveKeepalive(bodySize, configuredKeepalive);\n\n try {\n const response = await fetch(url, {\n ...requestInit,\n keepalive: keepaliveReservation.keepalive,\n });\n\n return this.handleResponse(response);\n } catch (err) {\n if (keepaliveReservation.keepalive && this.isFetchNetworkError(err)) {\n this.logDebug('Retrying failed keepalive request with keepalive disabled.');\n\n const response = await fetch(url, {\n ...requestInit,\n keepalive: false,\n });\n\n return this.handleResponse(response);\n }\n\n throw err;\n } finally {\n keepaliveReservation.release();\n }\n }\n\n private async handleResponse(response: Response): Promise<Response> {\n if (response.status === ACCEPTED) {\n const sessionExpired = response.headers.get('X-Faro-Session-Status') === 'invalid';\n\n if (sessionExpired) {\n this.extendFaroSession(this.config, this.logDebug);\n }\n }\n\n if (response.status === TOO_MANY_REQUESTS) {\n this.disabledUntil = this.getRetryAfterDate(response);\n this.logWarn(`Too many requests, backing off until ${this.disabledUntil}`);\n }\n\n // read the body so the connection can be closed\n response.text().catch(noop);\n return response;\n }\n\n private isFetchNetworkError(err: unknown): boolean {\n return err instanceof TypeError;\n }\n\n private async compress(body: string): Promise<Blob> {\n const stream = new ReadableStream({\n start(controller) {\n controller.enqueue(new TextEncoder().encode(body));\n controller.close();\n },\n }).pipeThrough(new CompressionStream('gzip'));\n\n const reader = stream.getReader();\n const chunks: BlobPart[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n chunks.push(value);\n }\n return new Blob(chunks);\n }\n\n private extendFaroSession(config: Config, logDebug: BaseExtension['logDebug']) {\n const SessionExpiredString = `Session expired`;\n\n const sessionTrackingConfig = config.sessionTracking;\n\n if (sessionTrackingConfig?.enabled) {\n const { fetchUserSession, storeUserSession } = getSessionManagerByConfig(sessionTrackingConfig);\n\n getUserSessionUpdater({ fetchUserSession, storeUserSession })({ forceSessionExtend: true });\n\n logDebug(`${SessionExpiredString} created new session.`);\n } else {\n logDebug(`${SessionExpiredString}.`);\n }\n }\n}\n"]}
@@ -8,11 +8,18 @@ export class SessionInstrumentation extends BaseInstrumentation {
8
8
  super(...arguments);
9
9
  this.name = '@grafana/faro-web-sdk:instrumentation-session';
10
10
  this.version = VERSION;
11
+ // Reads the session manager's adoption flag (set once the manager exists).
12
+ this.isAdoptingSession = () => false;
11
13
  }
12
14
  sendSessionStartEvent(meta) {
13
15
  var _a, _b;
14
16
  const session = meta.session;
15
17
  if (session && session.id !== ((_a = this.notifiedSession) === null || _a === void 0 ? void 0 : _a.id)) {
18
+ // Adopting another tab's session: track it but emit nothing (the creating tab already did).
19
+ if (this.isAdoptingSession()) {
20
+ this.notifiedSession = session;
21
+ return;
22
+ }
16
23
  if (this.notifiedSession && this.notifiedSession.id === ((_b = session.attributes) === null || _b === void 0 ? void 0 : _b['previousSession'])) {
17
24
  this.api.pushEvent(EVENT_SESSION_EXTEND, {}, undefined, { skipDedupe: true });
18
25
  this.notifiedSession = session;
@@ -67,17 +74,35 @@ export class SessionInstrumentation extends BaseInstrumentation {
67
74
  }
68
75
  registerBeforeSendHook(SessionManager) {
69
76
  var _a;
70
- const { updateSession } = new SessionManager();
77
+ const sessionManager = new SessionManager();
78
+ this.isAdoptingSession = sessionManager.isAdopting;
79
+ const { updateSession } = sessionManager;
80
+ // Most recent rotation updateSession performed. A batch buffered before the
81
+ // rotation is all stamped with the now-expired id, not just the triggering item.
82
+ let lastRotation;
71
83
  (_a = this.transports) === null || _a === void 0 ? void 0 : _a.addBeforeSendHooks((item) => {
72
- var _a, _b, _c;
84
+ var _a, _b, _c, _d;
85
+ const previousSessionId = (_a = this.metas.value.session) === null || _a === void 0 ? void 0 : _a.id;
73
86
  updateSession();
74
- const attributes = (_a = item.meta.session) === null || _a === void 0 ? void 0 : _a.attributes;
87
+ const currentSession = this.metas.value.session;
88
+ if (currentSession != null && previousSessionId != null && currentSession.id !== previousSessionId) {
89
+ lastRotation = { from: previousSessionId, to: currentSession };
90
+ }
91
+ // Re-stamp items still carrying the rotated-from session. Keyed on that id,
92
+ // so items from a genuinely earlier session (explicit setSession, which
93
+ // updateSession never rotates) keep their own sampling decision.
94
+ const reStamp = lastRotation != null && ((_b = item.meta.session) === null || _b === void 0 ? void 0 : _b.id) === lastRotation.from;
95
+ const session = reStamp ? lastRotation.to : item.meta.session;
96
+ const attributes = session === null || session === void 0 ? void 0 : session.attributes;
75
97
  if (attributes && (attributes === null || attributes === void 0 ? void 0 : attributes['isSampled']) === 'true') {
76
98
  let newItem = JSON.parse(JSON.stringify(item));
77
- const newAttributes = (_b = newItem.meta.session) === null || _b === void 0 ? void 0 : _b.attributes;
99
+ if (reStamp) {
100
+ newItem.meta.session = JSON.parse(JSON.stringify(lastRotation.to));
101
+ }
102
+ const newAttributes = (_c = newItem.meta.session) === null || _c === void 0 ? void 0 : _c.attributes;
78
103
  newAttributes === null || newAttributes === void 0 ? true : delete newAttributes['isSampled'];
79
104
  if (Object.keys(newAttributes !== null && newAttributes !== void 0 ? newAttributes : {}).length === 0) {
80
- (_c = newItem.meta.session) === null || _c === void 0 ? true : delete _c.attributes;
105
+ (_d = newItem.meta.session) === null || _d === void 0 ? true : delete _d.attributes;
81
106
  }
82
107
  return newItem;
83
108
  }