From 74cd71445ad8487e60ea9975693e90fe76ab0027 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 9 Dec 2022 14:01:35 +0100 Subject: [PATCH 1/5] ref(replay): Strip out capture exceptions stuff outside of DEBUG_BUILD --- packages/replay/src/replay.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 8870acb83fda..c89df40507e2 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -273,7 +273,7 @@ export class ReplayContainer implements ReplayContainerInterface { handleException(error: unknown): void { __DEBUG_BUILD__ && logger.error('[Replay]', error); - if (this.options._experiments && this.options._experiments.captureExceptions) { + if (__DEBUG_BUILD__ && this.options._experiments && this.options._experiments.captureExceptions) { captureException(error); } } From c39de676730f6ad5429a1bdbb4154cfed82a5034 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 9 Dec 2022 14:07:30 +0100 Subject: [PATCH 2/5] ref(replay): Make `options` and `recordingOptions` private --- packages/replay/src/replay.ts | 37 +++++++++------- packages/replay/src/types.ts | 1 + .../unit/index-integrationSettings.test.ts | 42 +++++++++---------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index c89df40507e2..ffc84538458c 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -67,9 +67,9 @@ export class ReplayContainer implements ReplayContainerInterface { /** * Options to pass to `rrweb.record()` */ - readonly recordingOptions: RecordingOptions; + private readonly _recordingOptions: RecordingOptions; - readonly options: ReplayPluginOptions; + private readonly _options: ReplayPluginOptions; private _performanceObserver: PerformanceObserver | null = null; @@ -123,11 +123,11 @@ export class ReplayContainer implements ReplayContainerInterface { }; constructor({ options, recordingOptions }: { options: ReplayPluginOptions; recordingOptions: RecordingOptions }) { - this.recordingOptions = recordingOptions; - this.options = options; + this._recordingOptions = recordingOptions; + this._options = options; - this._debouncedFlush = debounce(() => this.flush(), this.options.flushMinDelay, { - maxWait: this.options.flushMaxDelay, + this._debouncedFlush = debounce(() => this.flush(), this._options.flushMinDelay, { + maxWait: this._options.flushMaxDelay, }); } @@ -146,6 +146,11 @@ export class ReplayContainer implements ReplayContainerInterface { return this._isPaused; } + /** Get the replay integration options. */ + public getOptions(): ReplayPluginOptions { + return this._options; + } + /** * Initializes the plugin. * @@ -181,7 +186,7 @@ export class ReplayContainer implements ReplayContainerInterface { this.updateSessionActivity(); this.eventBuffer = createEventBuffer({ - useCompression: Boolean(this.options.useCompression), + useCompression: Boolean(this._options.useCompression), }); this.addListeners(); @@ -199,7 +204,7 @@ export class ReplayContainer implements ReplayContainerInterface { startRecording(): void { try { this._stopRecording = record({ - ...this.recordingOptions, + ...this._recordingOptions, // When running in error sampling mode, we need to overwrite `checkoutEveryNth` // Without this, it would record forever, until an error happens, which we don't want // instead, we'll always keep the last 60 seconds of replay before an error happened @@ -273,7 +278,7 @@ export class ReplayContainer implements ReplayContainerInterface { handleException(error: unknown): void { __DEBUG_BUILD__ && logger.error('[Replay]', error); - if (__DEBUG_BUILD__ && this.options._experiments && this.options._experiments.captureExceptions) { + if (__DEBUG_BUILD__ && this._options._experiments && this._options._experiments.captureExceptions) { captureException(error); } } @@ -295,10 +300,10 @@ export class ReplayContainer implements ReplayContainerInterface { loadSession({ expiry }: { expiry: number }): void { const { type, session } = getSession({ expiry, - stickySession: Boolean(this.options.stickySession), + stickySession: Boolean(this._options.stickySession), currentSession: this.session, - sessionSampleRate: this.options.sessionSampleRate, - errorSampleRate: this.options.errorSampleRate, + sessionSampleRate: this._options.sessionSampleRate, + errorSampleRate: this._options.errorSampleRate, }); // If session was newly created (i.e. was not loaded from storage), then @@ -480,7 +485,7 @@ export class ReplayContainer implements ReplayContainerInterface { // a previous session ID. In this case, we want to buffer events // for a set amount of time before flushing. This can help avoid // capturing replays of users that immediately close the window. - setTimeout(() => this.conditionalFlush(), this.options.initialFlushDelay); + setTimeout(() => this.conditionalFlush(), this._options.initialFlushDelay); // Cancel any previously debounced flushes to ensure there are no [near] // simultaneous flushes happening. The latter request should be @@ -953,8 +958,8 @@ export class ReplayContainer implements ReplayContainerInterface { preparedEvent.tags = { ...preparedEvent.tags, - sessionSampleRate: this.options.sessionSampleRate, - errorSampleRate: this.options.errorSampleRate, + sessionSampleRate: this._options.sessionSampleRate, + errorSampleRate: this._options.errorSampleRate, replayType: this.session?.sampled, }; @@ -1059,7 +1064,7 @@ export class ReplayContainer implements ReplayContainerInterface { /** Save the session, if it is sticky */ private _maybeSaveSession(): void { - if (this.session && this.options.stickySession) { + if (this.session && this._options.stickySession) { saveSession(this.session); } } diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index d49ede3cbbb4..d6892a8fbaaa 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -227,4 +227,5 @@ export interface ReplayContainer { flushImmediate(): void; triggerUserActivity(): void; addUpdate(cb: AddUpdateCallback): void; + getOptions(): ReplayPluginOptions; } diff --git a/packages/replay/test/unit/index-integrationSettings.test.ts b/packages/replay/test/unit/index-integrationSettings.test.ts index 08917a97c3e3..bea5deeab9a6 100644 --- a/packages/replay/test/unit/index-integrationSettings.test.ts +++ b/packages/replay/test/unit/index-integrationSettings.test.ts @@ -9,13 +9,13 @@ describe('integration settings', () => { it('sets the correct configuration when `blockAllMedia` is disabled', async () => { const { replay } = await mockSdk({ replayOptions: { blockAllMedia: false } }); - expect(replay.recordingOptions.blockSelector).toBe('[data-sentry-block]'); + expect(replay['_recordingOptions'].blockSelector).toBe('[data-sentry-block]'); }); it('sets the correct configuration when `blockSelector` is empty and `blockAllMedia` is enabled', async () => { const { replay } = await mockSdk({ replayOptions: { blockSelector: '' } }); - expect(replay.recordingOptions.blockSelector).toMatchInlineSnapshot( + expect(replay['_recordingOptions'].blockSelector).toMatchInlineSnapshot( '"img,image,svg,path,rect,area,video,object,picture,embed,map,audio"', ); }); @@ -25,7 +25,7 @@ describe('integration settings', () => { replayOptions: { blockSelector: '[data-test-blockSelector]' }, }); - expect(replay.recordingOptions.blockSelector).toMatchInlineSnapshot( + expect(replay['_recordingOptions'].blockSelector).toMatchInlineSnapshot( '"[data-test-blockSelector],img,image,svg,path,rect,area,video,object,picture,embed,map,audio"', ); }); @@ -48,7 +48,7 @@ describe('integration settings', () => { sentryOptions: { replaysSessionSampleRate: undefined }, }); - expect(replay.options.sessionSampleRate).toBe(0.5); + expect(replay.getOptions().sessionSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(1); }); @@ -58,21 +58,21 @@ describe('integration settings', () => { sentryOptions: { replaysSessionSampleRate: undefined }, }); - expect(replay.options.sessionSampleRate).toBe(0); + expect(replay.getOptions().sessionSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(1); }); it('works with defining settings in SDK', async () => { const { replay } = await mockSdk({ sentryOptions: { replaysSessionSampleRate: 0.5 }, replayOptions: {} }); - expect(replay.options.sessionSampleRate).toBe(0.5); + expect(replay.getOptions().sessionSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(0); }); it('works with defining 0 in SDK', async () => { const { replay } = await mockSdk({ sentryOptions: { replaysSessionSampleRate: 0 }, replayOptions: {} }); - expect(replay.options.sessionSampleRate).toBe(0); + expect(replay.getOptions().sessionSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(0); }); @@ -82,7 +82,7 @@ describe('integration settings', () => { replayOptions: { sessionSampleRate: 0.1 }, }); - expect(replay.options.sessionSampleRate).toBe(0.5); + expect(replay.getOptions().sessionSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(1); }); @@ -92,7 +92,7 @@ describe('integration settings', () => { replayOptions: { sessionSampleRate: 0.1 }, }); - expect(replay.options.sessionSampleRate).toBe(0); + expect(replay.getOptions().sessionSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(1); }); }); @@ -114,7 +114,7 @@ describe('integration settings', () => { sentryOptions: { replaysOnErrorSampleRate: undefined }, }); - expect(replay.options.errorSampleRate).toBe(0.5); + expect(replay.getOptions().errorSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(1); }); @@ -124,21 +124,21 @@ describe('integration settings', () => { sentryOptions: { replaysOnErrorSampleRate: undefined }, }); - expect(replay.options.errorSampleRate).toBe(0); + expect(replay.getOptions().errorSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(1); }); it('works with defining settings in SDK', async () => { const { replay } = await mockSdk({ sentryOptions: { replaysOnErrorSampleRate: 0.5 }, replayOptions: {} }); - expect(replay.options.errorSampleRate).toBe(0.5); + expect(replay.getOptions().errorSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(0); }); it('works with defining 0 in SDK', async () => { const { replay } = await mockSdk({ sentryOptions: { replaysOnErrorSampleRate: 0 }, replayOptions: {} }); - expect(replay.options.errorSampleRate).toBe(0); + expect(replay.getOptions().errorSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(0); }); @@ -148,7 +148,7 @@ describe('integration settings', () => { replayOptions: { errorSampleRate: 0.1 }, }); - expect(replay.options.errorSampleRate).toBe(0.5); + expect(replay.getOptions().errorSampleRate).toBe(0.5); expect(mockConsole).toBeCalledTimes(1); }); @@ -158,7 +158,7 @@ describe('integration settings', () => { replayOptions: { errorSampleRate: 0.1 }, }); - expect(replay.options.errorSampleRate).toBe(0); + expect(replay.getOptions().errorSampleRate).toBe(0); expect(mockConsole).toBeCalledTimes(1); }); }); @@ -168,25 +168,25 @@ describe('integration settings', () => { const { replay } = await mockSdk({ replayOptions: {} }); // Default is true - expect(replay.recordingOptions.maskTextSelector).toBe('*'); + expect(replay['_recordingOptions'].maskTextSelector).toBe('*'); }); it('works with true', async () => { const { replay } = await mockSdk({ replayOptions: { maskAllText: true } }); - expect(replay.recordingOptions.maskTextSelector).toBe('*'); + expect(replay['_recordingOptions'].maskTextSelector).toBe('*'); }); it('works with false', async () => { const { replay } = await mockSdk({ replayOptions: { maskAllText: false } }); - expect(replay.recordingOptions.maskTextSelector).toBe(undefined); + expect(replay['_recordingOptions'].maskTextSelector).toBe(undefined); }); it('overwrites custom maskTextSelector option', async () => { const { replay } = await mockSdk({ replayOptions: { maskAllText: true, maskTextSelector: '[custom]' } }); - expect(replay.recordingOptions.maskTextSelector).toBe('*'); + expect(replay['_recordingOptions'].maskTextSelector).toBe('*'); }); }); @@ -197,7 +197,7 @@ describe('integration settings', () => { sentryOptions: {}, }); - expect(replay.options._experiments).toEqual({ captureExceptions: true }); + expect(replay.getOptions()._experiments).toEqual({ captureExceptions: true }); }); it('works without defining _experiments in integration', async () => { @@ -206,7 +206,7 @@ describe('integration settings', () => { sentryOptions: {}, }); - expect(replay.options._experiments).toEqual({}); + expect(replay.getOptions()._experiments).toEqual({}); }); }); }); From d9aaa2249a8e2690d566f890daf650a7010b04d9 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 9 Dec 2022 14:51:02 +0100 Subject: [PATCH 3/5] ref(replay): Hide internal breadcrumbs behind experiments flag --- .../src/coreHandlers/handleGlobalEvent.ts | 25 ++++++++++++++----- packages/replay/src/types.ts | 5 +++- .../replay/src/util/addInternalBreadcrumb.ts | 21 ---------------- 3 files changed, 23 insertions(+), 28 deletions(-) delete mode 100644 packages/replay/src/util/addInternalBreadcrumb.ts diff --git a/packages/replay/src/coreHandlers/handleGlobalEvent.ts b/packages/replay/src/coreHandlers/handleGlobalEvent.ts index 1fa843a86cfd..86938757b38b 100644 --- a/packages/replay/src/coreHandlers/handleGlobalEvent.ts +++ b/packages/replay/src/coreHandlers/handleGlobalEvent.ts @@ -1,8 +1,8 @@ +import { addBreadcrumb } from '@sentry/core'; import { Event } from '@sentry/types'; import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants'; import type { ReplayContainer } from '../types'; -import { addInternalBreadcrumb } from '../util/addInternalBreadcrumb'; /** * Returns a listener to be added to `addGlobalEventProcessor(listener)`. @@ -39,11 +39,13 @@ export function handleGlobalEventListener(replay: ReplayContainer): (event: Even } const exc = event.exception?.values?.[0]; - addInternalBreadcrumb({ - message: `Tagging event (${event.event_id}) - ${event.message} - ${exc?.type || 'Unknown'}: ${ - exc?.value || 'n/a' - }`, - }); + if (__DEBUG_BUILD__ && replay.getOptions()._experiments?.traceInternals) { + addInternalBreadcrumb({ + message: `Tagging event (${event.event_id}) - ${event.message} - ${exc?.type || 'Unknown'}: ${ + exc?.value || 'n/a' + }`, + }); + } // Need to be very careful that this does not cause an infinite loop if ( @@ -72,3 +74,14 @@ export function handleGlobalEventListener(replay: ReplayContainer): (event: Even return event; }; } + +function addInternalBreadcrumb(arg: Parameters[0]): void { + const { category, level, message, ...rest } = arg; + + addBreadcrumb({ + category: category || 'console', + level: level || 'debug', + message: `[debug]: ${message}`, + ...rest, + }); +} diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index d6892a8fbaaa..7166fdae071f 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -113,7 +113,10 @@ export interface ReplayPluginOptions extends SessionOptions { * * Default: undefined */ - _experiments?: Partial<{ captureExceptions: boolean }>; + _experiments?: Partial<{ + captureExceptions: boolean; + traceInternals: boolean; + }>; } // These are optional for ReplayPluginOptions because the plugin sets default values diff --git a/packages/replay/src/util/addInternalBreadcrumb.ts b/packages/replay/src/util/addInternalBreadcrumb.ts deleted file mode 100644 index 2b8a10400764..000000000000 --- a/packages/replay/src/util/addInternalBreadcrumb.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { addBreadcrumb } from '@sentry/core'; - -import { isInternal } from './isInternal'; - -/** - * Wrapper for core SDK's `addBreadcrumb` only when used on `sentry.io` - */ -export function addInternalBreadcrumb(arg: Parameters[0]): void { - if (!isInternal()) { - return; - } - - const { category, level, message, ...rest } = arg; - - addBreadcrumb({ - category: category || 'console', - level: level || 'debug', - message: `[debug]: ${message}`, - ...rest, - }); -} From 99f2122741208919ceb1e545248cde3c286f8cd4 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 9 Dec 2022 14:49:07 +0100 Subject: [PATCH 4/5] ref(replay): Hide internal request tracing behind experiments flag --- .../replay/src/coreHandlers/handleFetch.ts | 11 ++++----- packages/replay/src/coreHandlers/handleXhr.ts | 9 +++++--- packages/replay/src/createPerformanceEntry.ts | 6 ----- packages/replay/src/util/isIngestHost.ts | 18 --------------- .../replay/src/util/shouldFilterRequest.ts | 23 +++++++++++++++++++ 5 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 packages/replay/src/util/isIngestHost.ts create mode 100644 packages/replay/src/util/shouldFilterRequest.ts diff --git a/packages/replay/src/coreHandlers/handleFetch.ts b/packages/replay/src/coreHandlers/handleFetch.ts index 1b6430d52413..961a18b638d6 100644 --- a/packages/replay/src/coreHandlers/handleFetch.ts +++ b/packages/replay/src/coreHandlers/handleFetch.ts @@ -1,7 +1,7 @@ import type { ReplayPerformanceEntry } from '../createPerformanceEntry'; import type { ReplayContainer } from '../types'; import { createPerformanceSpans } from '../util/createPerformanceSpans'; -import { isIngestHost } from '../util/isIngestHost'; +import { shouldFilterRequest } from '../util/shouldFilterRequest'; interface FetchHandlerData { args: Parameters; @@ -28,11 +28,6 @@ export function handleFetch(handlerData: FetchHandlerData): null | ReplayPerform const { startTimestamp, endTimestamp, fetchData, response } = handlerData; - // Do not capture fetches to Sentry ingestion endpoint - if (isIngestHost(fetchData.url)) { - return null; - } - return { type: 'resource.fetch', start: startTimestamp / 1000, @@ -60,6 +55,10 @@ export function handleFetchSpanListener(replay: ReplayContainer): (handlerData: return; } + if (shouldFilterRequest(replay, result.name)) { + return; + } + replay.addUpdate(() => { createPerformanceSpans(replay, [result]); // Returning true will cause `addUpdate` to not flush diff --git a/packages/replay/src/coreHandlers/handleXhr.ts b/packages/replay/src/coreHandlers/handleXhr.ts index 6577e91f3b92..a225345afe2f 100644 --- a/packages/replay/src/coreHandlers/handleXhr.ts +++ b/packages/replay/src/coreHandlers/handleXhr.ts @@ -1,7 +1,7 @@ import { ReplayPerformanceEntry } from '../createPerformanceEntry'; import type { ReplayContainer } from '../types'; import { createPerformanceSpans } from '../util/createPerformanceSpans'; -import { isIngestHost } from '../util/isIngestHost'; +import { shouldFilterRequest } from '../util/shouldFilterRequest'; // From sentry-javascript // e.g. https://github.com/getsentry/sentry-javascript/blob/c7fc025bf9fa8c073fdb56351808ce53909fbe45/packages/utils/src/instrument.ts#L180 @@ -47,8 +47,7 @@ function handleXhr(handlerData: XhrHandlerData): ReplayPerformanceEntry | null { const { method, url, status_code: statusCode } = handlerData.xhr.__sentry_xhr__ || {}; - // Do not capture fetches to Sentry ingestion endpoint - if (url === undefined || isIngestHost(url)) { + if (url === undefined) { return null; } @@ -79,6 +78,10 @@ export function handleXhrSpanListener(replay: ReplayContainer): (handlerData: Xh return; } + if (shouldFilterRequest(replay, result.name)) { + return; + } + replay.addUpdate(() => { createPerformanceSpans(replay, [result]); // Returning true will cause `addUpdate` to not flush diff --git a/packages/replay/src/createPerformanceEntry.ts b/packages/replay/src/createPerformanceEntry.ts index 33f6ae1a6a4d..c4fb293429dd 100644 --- a/packages/replay/src/createPerformanceEntry.ts +++ b/packages/replay/src/createPerformanceEntry.ts @@ -3,7 +3,6 @@ import { record } from 'rrweb'; import { WINDOW } from './constants'; import type { AllPerformanceEntry, PerformanceNavigationTiming, PerformancePaintTiming } from './types'; -import { isIngestHost } from './util/isIngestHost'; export interface ReplayPerformanceEntry { /** @@ -110,11 +109,6 @@ function createNavigationEntry(entry: PerformanceNavigationTiming) { function createResourceEntry(entry: PerformanceResourceTiming) { const { entryType, initiatorType, name, responseEnd, startTime, encodedBodySize, transferSize } = entry; - // Do not capture fetches to Sentry ingestion endpoint - if (isIngestHost(name)) { - return null; - } - // Core SDK handles these if (['fetch', 'xmlhttprequest'].includes(initiatorType)) { return null; diff --git a/packages/replay/src/util/isIngestHost.ts b/packages/replay/src/util/isIngestHost.ts deleted file mode 100644 index 471c64e6569d..000000000000 --- a/packages/replay/src/util/isIngestHost.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; - -import { isInternal } from './isInternal'; - -/** - * Checks is `targetHost` is a Sentry ingestion host - */ -export function isIngestHost(targetHost: string): boolean { - const { protocol, host } = getCurrentHub().getClient()?.getDsn() || {}; - - // XXX: Special case when this integration is used by Sentry on `sentry.io` - // We would like to capture network requests made to our ingest endpoints for debugging - if (isInternal()) { - return false; - } - - return targetHost.startsWith(`${protocol}://${host}`); -} diff --git a/packages/replay/src/util/shouldFilterRequest.ts b/packages/replay/src/util/shouldFilterRequest.ts new file mode 100644 index 000000000000..4faa1323f9c2 --- /dev/null +++ b/packages/replay/src/util/shouldFilterRequest.ts @@ -0,0 +1,23 @@ +import { getCurrentHub } from '@sentry/core'; + +import type { ReplayContainer } from '../types'; + +/** + * Check whether a given request URL should be filtered out. + */ +export function shouldFilterRequest(replay: ReplayContainer, url: string): boolean { + // If we enabled the `traceInternals` experiment, we want to trace everything + if (__DEBUG_BUILD__ && replay.getOptions()._experiments?.traceInternals) { + return false; + } + + return !_isSentryRequest(url); +} + +/** + * Checks wether a given URL belongs to the configured Sentry DSN. + */ +function _isSentryRequest(url: string): boolean { + const dsn = getCurrentHub().getClient()?.getDsn(); + return dsn ? url.includes(dsn.host) : false; +} From a9823658d1c9697b4cb04b7ae56f7835bf501d31 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Dec 2022 09:18:12 +0100 Subject: [PATCH 5/5] ref(replay): Remove unused `isInternal` file --- packages/replay/src/util/isInternal.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/replay/src/util/isInternal.ts diff --git a/packages/replay/src/util/isInternal.ts b/packages/replay/src/util/isInternal.ts deleted file mode 100644 index d04cc5256d58..000000000000 --- a/packages/replay/src/util/isInternal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { WINDOW } from '../constants'; -import { isBrowser } from './isBrowser'; - -/** - * Returns true if we are currently recording an internal to Sentry replay - * (e.g. on https://sentry.io ) - */ -export function isInternal(): boolean { - return isBrowser() && ['sentry.io', 'dev.getsentry.net'].includes(WINDOW.location.host); -}