Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡️ Remove classes in favour of functions #2885

Merged
merged 13 commits into from
Jul 25, 2024
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ module.exports = {
'local-rules/disallow-url-constructor-patched-values': 'error',
'no-restricted-syntax': [
'error',
{
selector: 'ClassDeclaration',
message: 'Classes are not allowed. Use functions instead.',
},
{
selector: 'ObjectExpression > SpreadElement',
message: 'Object spread is not authorized. Please use "assign" from the core package utils instead.',
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/domain/session/sessionManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Observable } from '../../tools/observable'
import type { Context } from '../../tools/serialisation/context'
import { ValueHistory } from '../../tools/valueHistory'
import { createValueHistory } from '../../tools/valueHistory'
import type { RelativeTime } from '../../tools/utils/timeUtils'
import { relativeNow, clocksOrigin, ONE_MINUTE } from '../../tools/utils/timeUtils'
import { DOM_EVENT, addEventListener, addEventListeners } from '../../browser/addEventListener'
Expand Down Expand Up @@ -46,7 +46,9 @@ export function startSessionManager<TrackingType extends string>(
const sessionStore = startSessionStore(configuration.sessionStoreStrategyType!, productKey, computeSessionState)
stopCallbacks.push(() => sessionStore.stop())

const sessionContextHistory = new ValueHistory<SessionContext<TrackingType>>(SESSION_CONTEXT_TIMEOUT_DELAY)
const sessionContextHistory = createValueHistory<SessionContext<TrackingType>>({
expireDelay: SESSION_CONTEXT_TIMEOUT_DELAY,
})
stopCallbacks.push(() => sessionContextHistory.stop())

sessionStore.renewObservable.subscribe(() => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/domain/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { NonErrorPrefix } from '../error/error.types'
import type { StackTrace } from '../../tools/stackTrace/computeStackTrace'
import { computeStackTrace } from '../../tools/stackTrace/computeStackTrace'
import { getConnectivity } from '../connectivity'
import { BoundedBuffer } from '../../tools/boundedBuffer'
import { createBoundedBuffer } from '../../tools/boundedBuffer'
import type { TelemetryEvent } from './telemetryEvent.types'
import type {
RawTelemetryConfiguration,
Expand Down Expand Up @@ -53,7 +53,7 @@ export interface Telemetry {
const TELEMETRY_EXCLUDED_SITES: string[] = [INTAKE_SITE_US1_FED]

// eslint-disable-next-line local-rules/disallow-side-effects
let preStartTelemetryBuffer = new BoundedBuffer()
let preStartTelemetryBuffer = createBoundedBuffer()
let onRawTelemetryEventCollected = (event: RawTelemetryEvent) => {
preStartTelemetryBuffer.add(() => onRawTelemetryEventCollected(event))
}
Expand Down Expand Up @@ -144,7 +144,7 @@ export function drainPreStartTelemetry() {
}

export function resetTelemetry() {
preStartTelemetryBuffer = new BoundedBuffer()
preStartTelemetryBuffer = createBoundedBuffer()
onRawTelemetryEventCollected = (event: RawTelemetryEvent) => {
preStartTelemetryBuffer.add(() => onRawTelemetryEventCollected(event))
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export { createPageExitObservable, PageExitEvent, PageExitReason, isPageExitReas
export * from './browser/addEventListener'
export * from './tools/timer'
export { initConsoleObservable, resetConsoleObservable, ConsoleLog } from './domain/console/consoleObservable'
export { BoundedBuffer } from './tools/boundedBuffer'
export { createBoundedBuffer, BoundedBuffer } from './tools/boundedBuffer'
export { catchUserErrors } from './tools/catchUserErrors'
export { createContextManager, ContextManager } from './domain/context/contextManager'
export { storeContextManager, removeStorageListeners } from './domain/context/storeContextManager'
Expand All @@ -120,7 +120,7 @@ export {
CustomerDataCompressionStatus,
} from './domain/context/customerDataTracker'
export { CustomerDataType } from './domain/context/contextConstants'
export { ValueHistory, ValueHistoryEntry, CLEAR_OLD_VALUES_INTERVAL } from './tools/valueHistory'
export { createValueHistory, ValueHistory, ValueHistoryEntry, CLEAR_OLD_VALUES_INTERVAL } from './tools/valueHistory'
export { readBytesFromStream } from './tools/readBytesFromStream'
export { STORAGE_POLL_DELAY } from './domain/session/sessionStore'
export { SESSION_STORE_KEY } from './domain/session/storeStrategies/sessionStoreStrategy'
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/tools/abstractLifeCycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type EventTypesWithoutData<EventMap> = {
[K in keyof EventMap]: EventMap[K] extends void ? K : never
}[keyof EventMap]

// eslint-disable-next-line no-restricted-syntax
export class AbstractLifeCycle<EventMap> {
private callbacks: { [key in keyof EventMap]?: Array<(data: any) => void> } = {}

Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/tools/boundedBuffer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BoundedBuffer } from './boundedBuffer'
import { createBoundedBuffer } from './boundedBuffer'

describe('BoundedBuffer', () => {
it('collect and drain the callbacks', () => {
const spy = jasmine.createSpy<() => void>()
const buffered = new BoundedBuffer()
const buffered = createBoundedBuffer()

buffered.add(spy)
expect(spy.calls.count()).toBe(0)
Expand All @@ -17,7 +17,7 @@ describe('BoundedBuffer', () => {

it('store at most 500 callbacks', () => {
const spy = jasmine.createSpy<() => void>()
const buffered = new BoundedBuffer()
const buffered = createBoundedBuffer()
const limit = 500

for (let i = 0; i < limit + 1; i += 1) {
Expand All @@ -30,7 +30,7 @@ describe('BoundedBuffer', () => {

it('removes a callback', () => {
const spy = jasmine.createSpy<() => void>()
const buffered = new BoundedBuffer()
const buffered = createBoundedBuffer()

buffered.add(spy)
buffered.remove(spy)
Expand Down
32 changes: 22 additions & 10 deletions packages/core/src/tools/boundedBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@ import { removeItem } from './utils/arrayUtils'

const BUFFER_LIMIT = 500

export class BoundedBuffer<T = void> {
private buffer: Array<(arg: T) => void> = []
export interface BoundedBuffer<T = void> {
add: (callback: (arg: T) => void) => void
remove: (callback: (arg: T) => void) => void
drain: (arg: T) => void
}

export function createBoundedBuffer<T = void>(): BoundedBuffer<T> {
const buffer: Array<(arg: T) => void> = []

add(callback: (arg: T) => void) {
const length = this.buffer.push(callback)
const add: BoundedBuffer<T>['add'] = (callback: (arg: T) => void) => {
const length = buffer.push(callback)
if (length > BUFFER_LIMIT) {
this.buffer.splice(0, 1)
buffer.splice(0, 1)
}
}

remove(callback: (arg: T) => void) {
removeItem(this.buffer, callback)
const remove: BoundedBuffer<T>['remove'] = (callback: (arg: T) => void) => {
removeItem(buffer, callback)
}

const drain = (arg: T) => {
buffer.forEach((callback) => callback(arg))
buffer.length = 0
}

drain(arg: T) {
this.buffer.forEach((callback) => callback(arg))
this.buffer.length = 0
return {
add,
remove,
drain,
}
}
1 change: 1 addition & 0 deletions packages/core/src/tools/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface Subscription {
unsubscribe: () => void
}

// eslint-disable-next-line no-restricted-syntax
export class Observable<T> {
private observers: Array<(data: T) => void> = []
private onLastUnsubscribe?: () => void
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/tools/valueHistory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Clock } from '../../test'
import { mockClock } from '../../test'
import type { Duration, RelativeTime } from './utils/timeUtils'
import { addDuration, ONE_MINUTE } from './utils/timeUtils'
import { CLEAR_OLD_VALUES_INTERVAL, ValueHistory } from './valueHistory'
import { CLEAR_OLD_VALUES_INTERVAL, type ValueHistory, createValueHistory } from './valueHistory'

const EXPIRE_DELAY = 10 * ONE_MINUTE
const MAX_ENTRIES = 5
Expand All @@ -13,7 +13,7 @@ describe('valueHistory', () => {

beforeEach(() => {
clock = mockClock()
valueHistory = new ValueHistory(EXPIRE_DELAY, MAX_ENTRIES)
valueHistory = createValueHistory({ expireDelay: EXPIRE_DELAY, maxEntries: MAX_ENTRIES })
})

afterEach(() => {
Expand Down
73 changes: 42 additions & 31 deletions packages/core/src/tools/valueHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,58 @@ export interface ValueHistoryEntry<T> {
export const CLEAR_OLD_VALUES_INTERVAL = ONE_MINUTE

/**
* Store and keep track of values spans. This whole class assumes that values are added in
* Store and keep track of values spans. This whole cache assumes that values are added in
* chronological order (i.e. all entries have an increasing start time).
*/
export class ValueHistory<Value> {
private entries: Array<ValueHistoryEntry<Value>> = []
private clearOldValuesInterval: TimeoutId

constructor(
private expireDelay: number,
private maxEntries?: number
) {
this.clearOldValuesInterval = setInterval(() => this.clearOldValues(), CLEAR_OLD_VALUES_INTERVAL)
export interface ValueHistory<Value> {
add: (value: Value, startTime: RelativeTime) => ValueHistoryEntry<Value>
find: (startTime?: RelativeTime, options?: { returnInactive: boolean }) => Value | undefined

closeActive: (endTime: RelativeTime) => void
findAll: (startTime?: RelativeTime, duration?: Duration) => Value[]
reset: () => void
stop: () => void
}

export function createValueHistory<Value>({
expireDelay,
maxEntries,
}: {
expireDelay: number
maxEntries?: number
}): ValueHistory<Value> {
let entries: Array<ValueHistoryEntry<Value>> = []
const clearOldValuesInterval: TimeoutId = setInterval(() => clearOldValues(), CLEAR_OLD_VALUES_INTERVAL)

function clearOldValues() {
const oldTimeThreshold = relativeNow() - expireDelay
while (entries.length > 0 && entries[entries.length - 1].endTime < oldTimeThreshold) {
entries.pop()
}
}

/**
* Add a value to the history associated with a start time. Returns a reference to this newly
* added entry that can be removed or closed.
*/
add(value: Value, startTime: RelativeTime): ValueHistoryEntry<Value> {
function add(value: Value, startTime: RelativeTime): ValueHistoryEntry<Value> {
const entry: ValueHistoryEntry<Value> = {
value,
startTime,
endTime: END_OF_TIMES,
remove: () => {
removeItem(this.entries, entry)
removeItem(entries, entry)
},
close: (endTime: RelativeTime) => {
entry.endTime = endTime
},
}

if (this.maxEntries && this.entries.length >= this.maxEntries) {
this.entries.pop()
if (maxEntries && entries.length >= maxEntries) {
entries.pop()
}

this.entries.unshift(entry)
entries.unshift(entry)

return entry
}
Expand All @@ -63,11 +79,11 @@ export class ValueHistory<Value> {
*
* If `option.returnInactive` is true, returns the value at `startTime` (active or not).
*/
find(
function find(
startTime: RelativeTime = END_OF_TIMES,
options: { returnInactive: boolean } = { returnInactive: false }
): Value | undefined {
for (const entry of this.entries) {
for (const entry of entries) {
if (entry.startTime <= startTime) {
if (options.returnInactive || startTime <= entry.endTime) {
return entry.value
Expand All @@ -81,8 +97,8 @@ export class ValueHistory<Value> {
* Helper function to close the currently active value, if any. This method assumes that entries
* are not overlapping.
*/
closeActive(endTime: RelativeTime) {
const latestEntry = this.entries[0]
function closeActive(endTime: RelativeTime) {
const latestEntry = entries[0]
if (latestEntry && latestEntry.endTime === END_OF_TIMES) {
latestEntry.close(endTime)
}
Expand All @@ -93,31 +109,26 @@ export class ValueHistory<Value> {
* or all values that were active during `startTime` if no duration is provided,
* or all currently active values if no `startTime` is provided.
*/
findAll(startTime: RelativeTime = END_OF_TIMES, duration = 0 as Duration): Value[] {
function findAll(startTime: RelativeTime = END_OF_TIMES, duration = 0 as Duration): Value[] {
const endTime = addDuration(startTime, duration)
return this.entries
return entries
.filter((entry) => entry.startTime <= endTime && startTime <= entry.endTime)
.map((entry) => entry.value)
}

/**
* Remove all entries from this collection.
*/
reset() {
this.entries = []
function reset() {
entries = []
}

/**
* Stop internal garbage collection of past entries.
*/
stop() {
clearInterval(this.clearOldValuesInterval)
function stop() {
clearInterval(clearOldValuesInterval)
}

private clearOldValues() {
const oldTimeThreshold = relativeNow() - this.expireDelay
while (this.entries.length > 0 && this.entries[this.entries.length - 1].endTime < oldTimeThreshold) {
this.entries.pop()
}
}
return { add, find, closeActive, findAll, reset, stop }
}
4 changes: 2 additions & 2 deletions packages/core/src/transport/batch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createMockFlushController } from '../../test'
import { display } from '../tools/display'
import type { Encoder } from '../tools/encoder'
import { createIdentityEncoder } from '../tools/encoder'
import { Batch } from './batch'
import { createBatch, type Batch } from './batch'
import type { HttpRequest } from './httpRequest'

describe('batch', () => {
Expand All @@ -30,7 +30,7 @@ describe('batch', () => {
} satisfies HttpRequest
flushController = createMockFlushController()
encoder = createIdentityEncoder()
batch = new Batch(encoder, transport, flushController, MESSAGE_BYTES_LIMIT)
batch = createBatch({ encoder, request: transport, flushController, messageBytesLimit: MESSAGE_BYTES_LIMIT })
})

it('should send a message', () => {
Expand Down
Loading