-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathlocationChangeObservable.ts
More file actions
76 lines (67 loc) · 2.45 KB
/
Copy pathlocationChangeObservable.ts
File metadata and controls
76 lines (67 loc) · 2.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import {
addEventListener,
DOM_EVENT,
globalObject,
instrumentMethod,
Observable,
shallowClone,
} from '@datadog/browser-core'
import type { RumConfiguration } from '../domain/configuration'
export interface LocationChange {
oldLocation: Readonly<Location>
newLocation: Readonly<Location>
}
export function createLocationChangeObservable(configuration: RumConfiguration) {
let currentLocation = shallowClone(globalObject.location)
return new Observable<LocationChange>((observable) => {
const { stop: stopHistoryTracking } = trackHistory(configuration, onLocationChange)
const { stop: stopHashTracking } = trackHash(configuration, onLocationChange)
function onLocationChange() {
if (currentLocation.href === location.href) {
return
}
const newLocation = shallowClone(location)
observable.notify({
newLocation,
oldLocation: currentLocation,
})
currentLocation = newLocation
}
return () => {
stopHistoryTracking()
stopHashTracking()
}
})
}
function trackHistory(configuration: RumConfiguration, onHistoryChange: () => void) {
const { stop: stopInstrumentingPushState } = instrumentMethod(
getHistoryInstrumentationTarget('pushState'),
'pushState',
({ onPostCall }) => {
onPostCall(onHistoryChange)
}
)
const { stop: stopInstrumentingReplaceState } = instrumentMethod(
getHistoryInstrumentationTarget('replaceState'),
'replaceState',
({ onPostCall }) => {
onPostCall(onHistoryChange)
}
)
const { stop: removeListener } = addEventListener(configuration, window, DOM_EVENT.POP_STATE, onHistoryChange)
return {
stop: () => {
stopInstrumentingPushState()
stopInstrumentingReplaceState()
removeListener()
},
}
}
function trackHash(configuration: RumConfiguration, onHashChange: () => void) {
return addEventListener(configuration, window, DOM_EVENT.HASH_CHANGE, onHashChange)
}
function getHistoryInstrumentationTarget(methodName: 'pushState' | 'replaceState') {
// Ideally we should always instument the method on the prototype, however some frameworks (e.g [Next.js](https://github.com/vercel/next.js/blob/d3f5532065f3e3bb84fb54bd2dfd1a16d0f03a21/packages/next/src/client/components/app-router.tsx#L429))
// are wrapping the instance method. In that case we should also wrap the instance method.
return Object.prototype.hasOwnProperty.call(history, methodName) ? history : History.prototype
}