Skip to content

Commit 185453c

Browse files
authored
feat: external session (#1648)
1 parent 7152d85 commit 185453c

File tree

12 files changed

+283
-80
lines changed

12 files changed

+283
-80
lines changed

packages/integration-tests/src/tests/native/native.ejs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
<meta charset="UTF-8">
55
<title>Native</title>
66
<script>
7-
window.SplunkRumNative = {
8-
getNativeSessionId: function() {
9-
return '12341234123412341234123412341234';
7+
window.SplunkRumExternal = {
8+
getSessionMetadata: function() {
9+
const now = Date.now();
10+
return {
11+
anonymousUserId: 'external-anonymous-user-id',
12+
sessionId: '12341234123412341234123412341234',
13+
sessionLastActivity: now,
14+
sessionStart: now - 1000
15+
};
1016
}
1117
};
1218
</script>

packages/integration-tests/src/tests/native/native.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import { expect } from '@playwright/test'
1919

2020
import { test } from '../../utils/test'
2121

22-
test.describe('native', () => {
23-
test('native session id integration', async ({ recordPage }) => {
22+
test.describe('external', () => {
23+
test('external session id integration', async ({ recordPage }) => {
2424
await recordPage.goTo('/native/native.ejs')
2525

2626
await recordPage.waitForSpans((spans) => spans.some((s) => s.name === 'documentFetch'))

packages/session-recorder/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const createSessionReplaySpanIfAllowed = (spanName: SpanName, sessionId: string
9494
// Check if session is managed by native SDK
9595
const SplunkRum = getGlobal<SplunkOtelWebType>()
9696
const sessionState = SplunkRum?.sessionManager?.getSessionState()
97-
if (sessionState?.source === 'native') {
97+
if (sessionState?.source === 'external') {
9898
log.debug('Session replay span not created - recording is managed by native SDK', { sessionId, spanName })
9999
return
100100
}

packages/web/src/index.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export { SplunkZipkinExporter } from './exporters/zipkin'
8585
export * from './session-based-sampler'
8686
export * from './splunk-web-tracer-provider'
8787
import { PrivacyManager, SessionManager, SessionState, StorageManager, UserManager } from './managers'
88+
import { ExternalSessionMetadata, isValidExternalSessionMetadata } from './types/external-session-metadata'
8889
import { getElementXPath, getTextFromNode } from './utils/index'
8990

9091
interface SplunkOtelWebConfigInternal extends SplunkOtelWebConfig {
@@ -121,6 +122,7 @@ const OPTIONS_DEFAULTS: SplunkOtelWebConfigInternal = {
121122
instrumentations: {},
122123
persistence: 'cookie',
123124
rumAccessToken: undefined,
125+
sessionMetadata: undefined,
124126
spanProcessor: {
125127
factory: (exporter, config) => new BatchSpanProcessor(exporter, config),
126128
},
@@ -220,6 +222,8 @@ export interface SplunkOtelWebType extends SplunkOtelWebEventTarget {
220222
*/
221223
getSessionId: () => string | undefined
222224

225+
getSessionMetadata: () => ExternalSessionMetadata
226+
223227
getSessionState: () => SessionState | undefined
224228

225229
init: (options: SplunkOtelWebConfig) => void
@@ -330,6 +334,28 @@ export const SplunkRum: SplunkOtelWebType = {
330334
}
331335
},
332336

337+
getSessionMetadata(): ExternalSessionMetadata {
338+
if (!inited || !this.sessionManager) {
339+
return null
340+
}
341+
342+
const session = this.sessionManager.getSessionMetadata()
343+
if (!session) {
344+
return null
345+
}
346+
347+
const metadata: ExternalSessionMetadata = {
348+
...session,
349+
}
350+
351+
const anonymousUserId = this.getAnonymousId()
352+
if (anonymousUserId) {
353+
metadata.anonymousUserId = anonymousUserId
354+
}
355+
356+
return metadata
357+
},
358+
333359
getSessionState() {
334360
if (!inited) {
335361
return
@@ -514,10 +540,20 @@ export const SplunkRum: SplunkOtelWebType = {
514540
})
515541

516542
this.resource = new Resource(resourceAttrs)
517-
this.sessionManager = new SessionManager(storageManager)
518-
this.userManager = new UserManager(userTrackingMode, storageManager)
543+
544+
let sessionMetadataFromOptions: NonNullable<ExternalSessionMetadata> | undefined
545+
if (processedOptions.sessionMetadata && isValidExternalSessionMetadata(processedOptions.sessionMetadata)) {
546+
sessionMetadataFromOptions = processedOptions.sessionMetadata
547+
}
548+
549+
this.sessionManager = new SessionManager(storageManager, sessionMetadataFromOptions)
550+
this.userManager = new UserManager(
551+
userTrackingMode,
552+
storageManager,
553+
sessionMetadataFromOptions?.anonymousUserId,
554+
)
519555
_sessionStateUnsubscribe = this.sessionManager.subscribe(({ currentState, previousState }) => {
520-
if (currentState.isNew && currentState.source !== 'native') {
556+
if (currentState.isNew && currentState.source !== 'external') {
521557
provider.getTracer('splunk-sessions').startSpan('session.start').end()
522558
}
523559

packages/web/src/managers/session-manager/session-manager.test.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import { diag } from '@opentelemetry/api'
2020
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2121

22+
import type { ExternalSessionMetadata } from '../../types/external-session-metadata'
23+
2224
import { StorageManager } from '../storage'
2325
import { SESSION_ID_LENGTH, SESSION_INACTIVITY_TIMEOUT_MS } from './constants'
2426
import { SessionManager } from './session-manager'
@@ -27,10 +29,24 @@ vi.mock('../../utils/is-trusted-event', () => ({
2729
isTrustedEvent: vi.fn(() => true),
2830
}))
2931

32+
function createExternalSessionMetadata(
33+
overrides: Partial<NonNullable<ExternalSessionMetadata>> = {},
34+
): NonNullable<ExternalSessionMetadata> {
35+
const now = Date.now()
36+
37+
return {
38+
anonymousUserId: 'external-anonymous-id',
39+
sessionId: 'external-session-id',
40+
sessionLastActivity: now,
41+
sessionStart: now - 1000,
42+
...overrides,
43+
}
44+
}
45+
3046
declare global {
3147
interface Window {
32-
SplunkRumNative?: {
33-
getNativeSessionId(): string
48+
SplunkRumExternal?: {
49+
getSessionMetadata(): NonNullable<ExternalSessionMetadata>
3450
}
3551
}
3652
}
@@ -40,7 +56,7 @@ describe('SessionManager', () => {
4056
let storageManager: StorageManager
4157

4258
beforeEach(() => {
43-
window.SplunkRumNative = undefined
59+
window.SplunkRumExternal = undefined
4460
storageManager = new StorageManager({
4561
persistence: 'cookie',
4662
})
@@ -103,9 +119,11 @@ describe('SessionManager', () => {
103119
expect(sessionManager.getSessionId()).toHaveLength(SESSION_ID_LENGTH)
104120
})
105121

106-
it('should use native session when available', () => {
107-
window.SplunkRumNative = {
108-
getNativeSessionId: vi.fn().mockReturnValue('native-session-id'),
122+
it('should use external session when available', () => {
123+
window.SplunkRumExternal = {
124+
getSessionMetadata: vi
125+
.fn()
126+
.mockReturnValue(createExternalSessionMetadata({ sessionId: 'external-session-id' })),
109127
}
110128

111129
const persistedSession = {
@@ -122,40 +140,42 @@ describe('SessionManager', () => {
122140

123141
sessionManager = new SessionManager(storageManager)
124142

125-
expect(sessionManager.getSessionId()).toBe('native-session-id')
143+
expect(sessionManager.getSessionId()).toBe('external-session-id')
126144
})
127145
})
128146

129147
describe('Static Methods', () => {
130-
describe('getNativeSessionId', () => {
131-
it('should return null when SplunkRumNative is not available', () => {
132-
window.SplunkRumNative = undefined
148+
describe('getExternalSession', () => {
149+
it('should return null when SplunkRumExternal is not available', () => {
150+
window.SplunkRumExternal = undefined
133151

134-
expect(SessionManager.getNativeSessionId()).toBeNull()
152+
expect(SessionManager.getExternalSession()).toBeNull()
135153
})
136154

137-
it('should return native session ID when available', () => {
138-
window.SplunkRumNative = {
139-
getNativeSessionId: vi.fn().mockReturnValue('native-id'),
155+
it('should return external session when available', () => {
156+
window.SplunkRumExternal = {
157+
getSessionMetadata: vi
158+
.fn()
159+
.mockReturnValue(createExternalSessionMetadata({ sessionId: 'external-id' })),
140160
}
141161

142-
expect(SessionManager.getNativeSessionId()).toBe('native-id')
162+
expect(SessionManager.getExternalSession()?.id).toBe('external-id')
143163
})
144164
})
145165

146-
describe('hasNativeSessionId', () => {
147-
it('should return false when SplunkRumNative is not available', () => {
148-
window.SplunkRumNative = undefined
166+
describe('hasExternalSession', () => {
167+
it('should return false when SplunkRumExternal is not available', () => {
168+
window.SplunkRumExternal = undefined
149169

150-
expect(SessionManager.hasNativeSessionId()).toBe(false)
170+
expect(SessionManager.hasExternalSession()).toBe(false)
151171
})
152172

153-
it('should return true when SplunkRumNative is available', () => {
154-
window.SplunkRumNative = {
155-
getNativeSessionId: vi.fn(),
173+
it('should return true when SplunkRumExternal is available', () => {
174+
window.SplunkRumExternal = {
175+
getSessionMetadata: vi.fn().mockReturnValue(createExternalSessionMetadata()),
156176
}
157177

158-
expect(SessionManager.hasNativeSessionId()).toBe(true)
178+
expect(SessionManager.hasExternalSession()).toBe(true)
159179
})
160180
})
161181
})
@@ -427,13 +447,15 @@ describe('SessionManager', () => {
427447
expect(sessionManager.getSessionId()).not.toBe('persisted-session-id')
428448
})
429449

430-
it('should not extend session when native session is available', () => {
450+
it('should not extend session when external session is available', () => {
431451
const diagDebugSpy = vi.spyOn(diag, 'debug')
432452

433453
sessionManager.start()
434454

435-
window.SplunkRumNative = {
436-
getNativeSessionId: vi.fn().mockReturnValue('native-id'),
455+
window.SplunkRumExternal = {
456+
getSessionMetadata: vi
457+
.fn()
458+
.mockReturnValue(createExternalSessionMetadata({ sessionId: 'external-id' })),
437459
}
438460

439461
const keydownEvent = new KeyboardEvent('keydown', {
@@ -446,7 +468,7 @@ describe('SessionManager', () => {
446468
window.dispatchEvent(keydownEvent)
447469

448470
expect(diagDebugSpy).toHaveBeenCalledWith(
449-
'SessionManager: Native session ID detected. Session extension or creation is managed natively.',
471+
'SessionManager: External session ID detected. Session extension or creation is managed externally.',
450472
)
451473
})
452474
})

0 commit comments

Comments
 (0)