Skip to content

Commit

Permalink
improve types
Browse files Browse the repository at this point in the history
  • Loading branch information
potty committed Jan 30, 2025
1 parent 6a8fcab commit 9415cb9
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 33 deletions.
18 changes: 6 additions & 12 deletions packages/session-recorder/src/OTLPLogExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
import { gzipSync, gzip, strToU8 } from 'fflate'
import type { JsonArray, JsonObject, JsonValue } from 'type-fest'

import { IAnyValue, Log, WindowWithSessionReplay, SessionReplay } from './types'
import { IAnyValue, Log } from './types'
import { VERSION } from './version.js'
import { getSessionReplayGlobal } from './session-replay/utils'

interface OTLPLogExporterConfig {
beaconUrl: string
Expand Down Expand Up @@ -84,14 +85,6 @@ function convertToAnyValue(value: JsonValue): IAnyValue {
return {}
}

const getSessionReplay = (): SessionReplay | null => {
if ((window as WindowWithSessionReplay).SessionReplay) {
return (window as WindowWithSessionReplay).SessionReplay
}

return null
}

export default class OTLPLogExporter {
config: OTLPLogExporterConfig

Expand Down Expand Up @@ -157,19 +150,20 @@ export default class OTLPLogExporter {
}

const compressAsync = async (data: Uint8Array): Promise<Uint8Array | Blob> => {
if (!getSessionReplay()) {
const SessionReplay = getSessionReplayGlobal()
if (!SessionReplay) {
console.warn('SessionReplay module undefined, fallback to gzip.')
return compressGzipAsync(data)
}

const isCompressionSupported = await getSessionReplay().isCompressionSupported()
const isCompressionSupported = await SessionReplay.isCompressionSupported()
if (!isCompressionSupported) {
console.warn('Compression is not supported, fallback to gzip.')
return compressGzipAsync(data)
}

const dataBlob = new Blob([data])
return getSessionReplay().compressData(dataBlob.stream(), 'gzip')
return SessionReplay.compressData(dataBlob.stream(), 'gzip')
}

const compressGzipAsync = async (data: Uint8Array): Promise<Uint8Array> =>
Expand Down
32 changes: 20 additions & 12 deletions packages/session-recorder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import type { Resource } from '@opentelemetry/resources'
import type { SplunkOtelWebType } from '@splunk/otel-web'

import { loadRecorderBrowserScript } from './session-replay/load'
import { getSessionReplayPlainGlobal } from './session-replay/utils'
import { SessionReplayPlain as SessionReplayPlainType } from './session-replay/types'

interface BasicTracerProvider extends TracerProvider {
readonly resource: Resource
Expand Down Expand Up @@ -64,7 +66,7 @@ let paused = false
let eventCounter = 1
let logCounter = 1

let srp: any
let sessionReplay: SessionReplayPlainType | undefined

const SplunkRumRecorder = {
get inited(): boolean {
Expand Down Expand Up @@ -142,22 +144,28 @@ const SplunkRumRecorder = {

lastKnownSession = SplunkRum.getSessionId()

const SessionReplayPlain = getSessionReplayPlainGlobal()
if (!SessionReplayPlain) {
console.error('SessionReplayPlain is not available')
return
}

if (SplunkRum.isNewSessionId()) {
console.log('SplunkRum.isNewSessionId()', 'clearing')
;(window as any).SessionReplayPlain.clear()
SessionReplayPlain.clear()
}

sessionStartTime = Date.now()

srp = new (window as any).SessionReplayPlain({
sessionReplay = new SessionReplayPlain({
features: {
backgroundServiceSrc: 'https://domain.com/web/latest/background-service.html',
backgroundServiceSrc:
'https://session-replay-artifacts.ci.smartlook.cloud/web/latest/background-service.html',
cacheAssets: false,
iframes: false,
imageBitmap: false,
packAssets: false,
},
isDebug: true,
logLevel: 'debug',
maskAllInputs: false,
maskAllText: false,
Expand Down Expand Up @@ -187,10 +195,10 @@ const SplunkRumRecorder = {
// reset counters
eventCounter = 1
logCounter = 1
srp?.stop()
sessionReplay?.stop()
console.log('onSegment - Clearing assets')
;(window as any).SessionReplayPlain.clear()
srp?.start()
SessionReplayPlain.clear()
void sessionReplay?.start()
}

if (segment.metadata.startUnixMs > sessionStartTime + MAX_RECORDING_LENGTH) {
Expand Down Expand Up @@ -236,7 +244,7 @@ const SplunkRumRecorder = {
})

console.log('Starting SRP')
srp.start()
void sessionReplay.start()
inited = true
},

Expand All @@ -248,7 +256,7 @@ const SplunkRumRecorder = {
const oldPaused = paused
paused = false
if (!oldPaused) {
srp?.start()
void sessionReplay?.start()
tracer.startSpan('record resume').end()
}
},
Expand All @@ -258,7 +266,7 @@ const SplunkRumRecorder = {
}

if (paused) {
srp.stop()
sessionReplay.stop()
tracer.startSpan('record stop').end()
}

Expand All @@ -269,7 +277,7 @@ const SplunkRumRecorder = {
return
}

srp?.stop()
sessionReplay?.stop()
inited = false
},
}
Expand Down
97 changes: 97 additions & 0 deletions packages/session-recorder/src/session-replay/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
*
* Copyright 2020-2025 Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export interface WindowWithSessionReplay {
SessionReplay?: SessionReplayClass
SessionReplayPlain?: SessionReplayPlainClass
}

interface SessionReplayClassBase {
clear: () => void
}

interface SessionReplayBase {
start: () => Promise<void>
stop: () => void
}

export interface SessionReplayClass extends SessionReplayClassBase {
compressData: (dataToCompress: ReadableStream<Uint8Array>, format: 'deflate' | 'gzip') => Promise<Blob>
isCompressionSupported: () => Promise<boolean>
}

export interface SessionReplayPlainClass extends SessionReplayClassBase {
new (config: SessionReplayConfig): SessionReplayPlain
}

export type SessionReplayPlain = SessionReplayBase

type LogLevel = 'debug' | 'info' | 'warn' | 'error'

interface ConfigFeatures {
backgroundServiceSrc?: string
cacheAssets?: boolean
iframes?: boolean
imageBitmap?: boolean
packAssets?: boolean
}

export interface SessionReplayConfig {
features?: ConfigFeatures
logLevel?: LogLevel
maskAllInputs?: boolean
maskAllText?: boolean
maxExportIntervalMs?: number
onSegment: (segment: SessionReplayPlainSegment) => void
}

interface SessionReplayPlainSegment {
data: {
assets: ProcessedAsset[]
events: ReplayEvent[]
}
metadata: SessionReplayMetadata
}

interface SessionReplayMetadata {
'browser.instance.id': string
'endUnixMs': number
'format': 'plain' | 'binary' | 'binary-deflate'
'sdkVersion': string
'source': 'web'
'startUnixMs': number
'synthetic'?: true
}

interface ReplayEvent {
id: string
name: string
relativeTime: number
timestamp: number
windowId: number
}

type DataURL = `data:${string}`

type AssetURI = `blob://${string}` | `http://${string}` | `https://${string}` | `blob:${string}`

interface ProcessedAsset {
content?: DataURL | Uint8Array
mimetype?: string
timestamp: number
uri: AssetURI
}
34 changes: 34 additions & 0 deletions packages/session-recorder/src/session-replay/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
*
* Copyright 2020-2025 Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { WindowWithSessionReplay, SessionReplayPlainClass, SessionReplayClass } from './types'

export const getSessionReplayGlobal = (): SessionReplayClass | null => {
if ((window as WindowWithSessionReplay).SessionReplay) {
return (window as WindowWithSessionReplay).SessionReplay
}

return null
}

export const getSessionReplayPlainGlobal = (): SessionReplayPlainClass | null => {
if ((window as WindowWithSessionReplay).SessionReplayPlain) {
return (window as WindowWithSessionReplay).SessionReplayPlain
}

return null
}
9 changes: 0 additions & 9 deletions packages/session-recorder/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,3 @@ export interface IKeyValueList {
/** KeyValueList values */
values?: IKeyValue[] | null
}

export interface SessionReplay {
compressData: (dataToCompress: ReadableStream<Uint8Array>, format: 'deflate' | 'gzip') => Promise<Blob>
isCompressionSupported: () => Promise<boolean>
}

export interface WindowWithSessionReplay {
SessionReplay?: SessionReplay
}

0 comments on commit 9415cb9

Please sign in to comment.