-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: wrap fetch and add tracing headers (#1186)
Co-authored-by: Tom Owers <owerstom@gmail.com>
- Loading branch information
1 parent
61350f5
commit 48bf7f1
Showing
7 changed files
with
187 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// import { patch } from 'rrweb/typings/utils' | ||
// copied from https://github.com/rrweb-io/rrweb/blob/8aea5b00a4dfe5a6f59bd2ae72bb624f45e51e81/packages/rrweb/src/utils.ts#L129 | ||
// which was copied from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts | ||
import { isFunction } from '../../../utils/type-utils' | ||
|
||
export function patch( | ||
source: { [key: string]: any }, | ||
name: string, | ||
replacement: (...args: unknown[]) => unknown | ||
): () => void { | ||
try { | ||
if (!(name in source)) { | ||
return () => { | ||
// | ||
} | ||
} | ||
|
||
const original = source[name] as () => unknown | ||
const wrapped = replacement(original) | ||
|
||
// Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work | ||
// otherwise it'll throw "TypeError: Object.defineProperties called on non-object" | ||
if (isFunction(wrapped)) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
wrapped.prototype = wrapped.prototype || {} | ||
Object.defineProperties(wrapped, { | ||
__posthog_wrapped__: { | ||
enumerable: false, | ||
value: true, | ||
}, | ||
}) | ||
} | ||
|
||
source[name] = wrapped | ||
|
||
return () => { | ||
source[name] = original | ||
} | ||
} catch { | ||
return () => { | ||
// | ||
} | ||
// This can throw if multiple fill happens on a global object like XMLHttpRequest | ||
// Fixes https://github.com/getsentry/sentry-javascript/issues/2043 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { PostHog } from '../posthog-core' | ||
import { assignableWindow } from '../utils/globals' | ||
import { logger } from '../utils/logger' | ||
import { loadScript } from '../utils' | ||
import Config from '../config' | ||
import { isUndefined } from '../utils/type-utils' | ||
|
||
const LOGGER_PREFIX = '[TRACING-HEADERS]' | ||
|
||
export class TracingHeaders { | ||
private _restoreXHRPatch: (() => void) | undefined = undefined | ||
private _restoreFetchPatch: (() => void) | undefined = undefined | ||
|
||
constructor(private readonly instance: PostHog) {} | ||
|
||
private _loadScript(cb: () => void): void { | ||
if (assignableWindow.postHogTracingHeadersPatchFns) { | ||
// already loaded | ||
cb() | ||
} | ||
|
||
loadScript( | ||
this.instance.requestRouter.endpointFor('assets', `/static/tracing-headers.js?v=${Config.LIB_VERSION}`), | ||
(err) => { | ||
if (err) { | ||
logger.error(LOGGER_PREFIX + ' failed to load script', err) | ||
} | ||
cb() | ||
} | ||
) | ||
} | ||
public startIfEnabledOrStop() { | ||
if (this.instance.config.__add_tracing_headers) { | ||
this._loadScript(this._startCapturing) | ||
} else { | ||
this._restoreXHRPatch?.() | ||
this._restoreFetchPatch?.() | ||
// we don't want to call these twice so we reset them | ||
this._restoreXHRPatch = undefined | ||
this._restoreFetchPatch = undefined | ||
} | ||
} | ||
|
||
private _startCapturing = () => { | ||
// NB: we can assert sessionManager is present only because we've checked previously | ||
if (isUndefined(this._restoreXHRPatch)) { | ||
assignableWindow.postHogTracingHeadersPatchFns._patchXHR(this.instance.sessionManager!) | ||
} | ||
if (isUndefined(this._restoreFetchPatch)) { | ||
assignableWindow.postHogTracingHeadersPatchFns._patchFetch(this.instance.sessionManager!) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { SessionIdManager } from './sessionid' | ||
import { patch } from './extensions/replay/rrweb-plugins/patch' | ||
import { assignableWindow, window } from './utils/globals' | ||
|
||
const addTracingHeaders = (sessionManager: SessionIdManager, req: Request) => { | ||
const { sessionId, windowId } = sessionManager.checkAndGetSessionAndWindowId(true) | ||
req.headers.set('X-POSTHOG-SESSION-ID', sessionId) | ||
req.headers.set('X-POSTHOG-WINDOW-ID', windowId) | ||
} | ||
|
||
const patchFetch = (sessionManager: SessionIdManager): (() => void) => { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
return patch(window, 'fetch', (originalFetch: typeof fetch) => { | ||
return async function (url: URL | RequestInfo, init?: RequestInit | undefined) { | ||
// check IE earlier than this, we only initialize if Request is present | ||
// eslint-disable-next-line compat/compat | ||
const req = new Request(url, init) | ||
|
||
addTracingHeaders(sessionManager, req) | ||
|
||
return originalFetch(req) | ||
} | ||
}) | ||
} | ||
|
||
const patchXHR = (sessionManager: SessionIdManager): (() => void) => { | ||
return patch( | ||
// we can assert this is present because we've checked previously | ||
window!.XMLHttpRequest.prototype, | ||
'open', | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
(originalOpen: typeof XMLHttpRequest.prototype.open) => { | ||
return function ( | ||
method: string, | ||
url: string | URL, | ||
async = true, | ||
username?: string | null, | ||
password?: string | null | ||
) { | ||
// because this function is returned in its actual context `this` _is_ an XMLHttpRequest | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
const xhr = this as XMLHttpRequest | ||
|
||
// check IE earlier than this, we only initialize if Request is present | ||
// eslint-disable-next-line compat/compat | ||
const req = new Request(url) | ||
|
||
addTracingHeaders(sessionManager, req) | ||
|
||
return originalOpen.call(xhr, method, req.url, async, username, password) | ||
} | ||
} | ||
) | ||
} | ||
|
||
if (assignableWindow) { | ||
assignableWindow.postHogTracingHeadersPatchFns = { | ||
_patchFetch: patchFetch, | ||
_patchXHR: patchXHR, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters