Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions e2e/react-router/src/routes/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,42 @@ export default function Root() {
>
LDObserve.stop()
</button>
<div
style={{
marginTop: 8,
padding: 12,
border: '1px solid #ddd',
borderRadius: 6,
maxWidth: 720,
}}
>
<h3>Session Properties</h3>
<p>
Add custom session-level attributes via{' '}
<code>LDRecord.addSessionProperties</code>.
</p>
<textarea
style={{
width: '100%',
minHeight: 120,
fontFamily: 'monospace',
}}
defaultValue='{"plan":"pro","favoriteColor":"seafoam"}'
placeholder='{"key":"value"}'
/>
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
<button
onClick={() => {
const value =
document.querySelector('textarea')?.value ?? ''

LDRecord.addSessionProperties(JSON.parse(value))
}}
>
Apply session properties
</button>
</div>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('../client/workers/highlight-client-worker?worker&inline', () => {
class FakeWorker {
onmessage = (_e: any) => {}
postMessage = vi.fn((_message: any) => null)
}
return { default: FakeWorker }
})

import { RecordSDK } from '../sdk/record'
import { LDRecord } from '../sdk/LDRecord'
import { MessageType } from '../client/workers/types'

describe('Record addSessionProperties', () => {
let recordImpl: RecordSDK

beforeEach(() => {
recordImpl = new RecordSDK({
organizationID: '1',
environment: 'test',
sessionSecureID: 'test-session',
})
})

it('posts Properties message with session type to the worker', () => {
const postSpy = vi.spyOn((recordImpl as any)._worker, 'postMessage')

const props = { plan: 'pro', count: 1, active: true }
recordImpl.addSessionProperties(props)

expect(postSpy).toHaveBeenCalled()
const call = postSpy.mock.calls[0]?.[0] as any
expect(call?.message?.type).toBe(MessageType.Properties)
expect(call?.message?.propertyType).toEqual({ type: 'session' })
expect(call?.message?.propertiesObject).toMatchObject(props)
})

it('LDRecord proxies addSessionProperties to implementation', () => {
// Load the implementation into the LDRecord buffered proxy
LDRecord.load(recordImpl)

const spy = vi.spyOn(recordImpl, 'addSessionProperties')
const props = { foo: 'bar' }
LDRecord.addSessionProperties(props)

expect(spy).toHaveBeenCalledWith(props)
})
})
5 changes: 5 additions & 0 deletions sdk/highlight-run/src/api/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export interface Record {
* Stop the session recording.
*/
stop: () => void
/**
* Add custom session-level properties. These are attached to the current session
* and are searchable, but do not create timeline Track events.
*/
addSessionProperties: (properties: { [key: string]: any }) => void
/**
* Snapshot an HTML <canvas> element in WebGL manual snapshotting mode.
* See {@link https://www.highlight.io/docs/getting-started/browser/replay-configuration/canvas#manual-snapshotting}
Expand Down
4 changes: 4 additions & 0 deletions sdk/highlight-run/src/sdk/LDRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class _LDRecord extends BufferedClass<Record> implements Record {
return this._sdk.stop()
}

addSessionProperties(properties: { [key: string]: any }) {
return this._bufferCall('addSessionProperties', [properties])
}

getRecordingState() {
return this._isLoaded
? this._bufferCall('getRecordingState', [])
Expand Down
8 changes: 8 additions & 0 deletions sdk/highlight-run/src/sdk/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,14 @@ export class RecordSDK implements Record {
})
}

/**
* Add custom session-level properties. These are attached to the current session
* and are searchable, but do not create timeline Track events.
*/
addSessionProperties(properties: { [key: string]: any }) {
this.addProperties(properties, { type: 'session' })
}

async start(options?: StartOptions) {
if (
navigator?.userAgent?.includes('Googlebot') ||
Expand Down
Loading