diff --git a/app/scripts/background.js b/app/scripts/background.js index 45fcd0315c10..a563d0f81393 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -82,6 +82,8 @@ import { createOffscreen } from './offscreen'; /* eslint-enable import/first */ +import { COOKIE_ID_MARKETING_WHITELIST_ORIGINS } from './constants/marketing-site-whitelist'; + // eslint-disable-next-line @metamask/design-tokens/color-no-hex const BADGE_COLOR_APPROVAL = '#0376C9'; // eslint-disable-next-line @metamask/design-tokens/color-no-hex @@ -124,6 +126,7 @@ if (inTest || process.env.METAMASK_DEBUG) { } const phishingPageUrl = new URL(process.env.PHISHING_WARNING_PAGE_URL); + // normalized (adds a trailing slash to the end of the domain if it's missing) // the URL once and reuse it: const phishingPageHref = phishingPageUrl.toString(); @@ -883,10 +886,10 @@ export function setupController( senderUrl.origin === phishingPageUrl.origin && senderUrl.pathname === phishingPageUrl.pathname ) { - const portStream = + const portStreamForPhishingPage = overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); controller.setupPhishingCommunication({ - connectionStream: portStream, + connectionStream: portStreamForPhishingPage, }); } else { // this is triggered when a new tab is opened, or origin(url) is changed @@ -906,6 +909,18 @@ export function setupController( } }); } + if ( + senderUrl && + COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( + (origin) => origin === senderUrl.origin, + ) + ) { + const portStreamForCookieHandlerPage = + overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); + controller.setUpCookieHandlerCommunication({ + connectionStream: portStreamForCookieHandlerPage, + }); + } connectExternalExtension(remotePort); } }; diff --git a/app/scripts/constants/marketing-site-whitelist.ts b/app/scripts/constants/marketing-site-whitelist.ts new file mode 100644 index 000000000000..8ff0cfd2de78 --- /dev/null +++ b/app/scripts/constants/marketing-site-whitelist.ts @@ -0,0 +1,15 @@ +export const COOKIE_ID_MARKETING_WHITELIST = [ + 'https://metamask.io', + 'https://learn.metamask.io', + 'https://mmi-support.zendesk.com', + 'https://community.metamask.io', + 'https://support.metamask.io', +]; + +if (process.env.IN_TEST) { + COOKIE_ID_MARKETING_WHITELIST.push('http://127.0.0.1:8080'); +} + +// Extract the origin of each URL in the whitelist +export const COOKIE_ID_MARKETING_WHITELIST_ORIGINS = + COOKIE_ID_MARKETING_WHITELIST.map((url) => new URL(url).origin); diff --git a/app/scripts/constants/sentry-state.ts b/app/scripts/constants/sentry-state.ts index df0238210d96..db78f948c31d 100644 --- a/app/scripts/constants/sentry-state.ts +++ b/app/scripts/constants/sentry-state.ts @@ -158,6 +158,7 @@ export const SENTRY_BACKGROUND_STATE = { segmentApiCalls: false, traits: false, dataCollectionForMarketing: false, + marketingCampaignCookieId: true, }, NameController: { names: false, diff --git a/app/scripts/constants/stream.ts b/app/scripts/constants/stream.ts new file mode 100644 index 000000000000..8552b49357dd --- /dev/null +++ b/app/scripts/constants/stream.ts @@ -0,0 +1,17 @@ +// contexts +export const CONTENT_SCRIPT = 'metamask-contentscript'; +export const METAMASK_INPAGE = 'metamask-inpage'; + +// stream channels +export const METAMASK_PROVIDER = 'metamask-provider'; +export const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; +export const PHISHING_SAFELIST = 'metamask-phishing-safelist'; +export const PHISHING_STREAM = 'phishing'; + +// For more information about these legacy streams, see here: +// https://github.com/MetaMask/metamask-extension/issues/15491 +// TODO:LegacyProvider: Delete +export const LEGACY_CONTENT_SCRIPT = 'contentscript'; +export const LEGACY_INPAGE = 'inpage'; +export const LEGACY_PROVIDER = 'provider'; +export const LEGACY_PUBLIC_CONFIG = 'publicConfig'; diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ab2483c9f9a3..d0ff7119498b 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -9,6 +9,16 @@ import { getIsBrowserPrerenderBroken, } from '../../shared/modules/browser-runtime.utils'; import shouldInjectProvider from '../../shared/modules/provider-injection'; +import { + initializeCookieHandlerSteam, + isDetectedCookieMarketingSite, +} from './streams/cookie-handler-stream'; +import { logStreamDisconnectWarning } from './streams/shared'; +import { + METAMASK_COOKIE_HANDLER, + PHISHING_STREAM, + METAMASK_PROVIDER, +} from './constants/stream'; // contexts const CONTENT_SCRIPT = 'metamask-contentscript'; @@ -73,6 +83,11 @@ function setupPhishingPageStreams() { ); phishingPageChannel = phishingPageMux.createStream(PHISHING_SAFELIST); + phishingPageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + phishingPageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + phishingPageMux.ignoreStream(LEGACY_PROVIDER); + phishingPageMux.ignoreStream(METAMASK_PROVIDER); + phishingPageMux.ignoreStream(PHISHING_STREAM); } const setupPhishingExtStreams = () => { @@ -116,6 +131,11 @@ const setupPhishingExtStreams = () => { error, ), ); + phishingExtMux.ignoreStream(METAMASK_COOKIE_HANDLER); + phishingExtMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + phishingExtMux.ignoreStream(LEGACY_PROVIDER); + phishingExtMux.ignoreStream(METAMASK_PROVIDER); + phishingExtMux.ignoreStream(PHISHING_STREAM); // eslint-disable-next-line no-use-before-define phishingExtPort.onDisconnect.addListener(onDisconnectDestroyPhishingStreams); @@ -213,6 +233,11 @@ const setupPageStreams = () => { ); pageChannel = pageMux.createStream(PROVIDER); + pageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + pageMux.ignoreStream(LEGACY_PROVIDER); + pageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + pageMux.ignoreStream(PHISHING_SAFELIST); + pageMux.ignoreStream(PHISHING_STREAM); }; // The field below is used to ensure that replay is done only once for each restart. @@ -248,6 +273,10 @@ const setupExtensionStreams = () => { extensionPhishingStream = extensionMux.createStream('phishing'); extensionPhishingStream.once('data', redirectToPhishingWarning); + extensionMux.ignoreStream(METAMASK_COOKIE_HANDLER); + extensionMux.ignoreStream(LEGACY_PROVIDER); + extensionMux.ignoreStream(PHISHING_SAFELIST); + // eslint-disable-next-line no-use-before-define extensionPort.onDisconnect.addListener(onDisconnectDestroyStreams); }; @@ -288,6 +317,11 @@ const setupLegacyPageStreams = () => { legacyPageMux.createStream(LEGACY_PROVIDER); legacyPagePublicConfigChannel = legacyPageMux.createStream(LEGACY_PUBLIC_CONFIG); + + legacyPageMux.ignoreStream(METAMASK_COOKIE_HANDLER); + legacyPageMux.ignoreStream(METAMASK_PROVIDER); + legacyPageMux.ignoreStream(PHISHING_SAFELIST); + legacyPageMux.ignoreStream(PHISHING_STREAM); }; // TODO:LegacyProvider: Delete @@ -331,6 +365,10 @@ const setupLegacyExtensionStreams = () => { error, ), ); + legacyExtMux.ignoreStream(METAMASK_COOKIE_HANDLER); + legacyExtMux.ignoreStream(LEGACY_PROVIDER); + legacyExtMux.ignoreStream(PHISHING_SAFELIST); + legacyExtMux.ignoreStream(PHISHING_STREAM); }; /** @@ -431,19 +469,6 @@ function getNotificationTransformStream() { return stream; } -/** - * Error handler for page to extension stream disconnections - * - * @param {string} remoteLabel - Remote stream name - * @param {Error} error - Stream connection error - */ -function logStreamDisconnectWarning(remoteLabel, error) { - console.debug( - `MetaMask: Content script lost connection to "${remoteLabel}".`, - error, - ); -} - /** * The function notifies inpage when the extension stream connection is ready. When the * 'metamask_chainChanged' method is received from the extension, it implies that the @@ -525,6 +550,10 @@ const start = () => { return; } + if (isDetectedCookieMarketingSite) { + initializeCookieHandlerSteam(); + } + if (shouldInjectProvider()) { initStreams(); diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 8aafa0893d53..5fa34ac0cbd1 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -155,6 +155,7 @@ export default class MetaMetricsController { participateInMetaMetrics: null, metaMetricsId: null, dataCollectionForMarketing: null, + marketingCampaignCookieId: null, eventsBeforeMetricsOptIn: [], traits: {}, previousUserTraits: {}, @@ -466,6 +467,8 @@ export default class MetaMetricsController { if (participateInMetaMetrics) { this.trackEventsAfterMetricsOptIn(); this.clearEventsAfterMetricsOptIn(); + } else if (this.state.marketingCampaignCookieId) { + this.setMarketingCampaignCookieId(null); } ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) @@ -477,10 +480,20 @@ export default class MetaMetricsController { setDataCollectionForMarketing(dataCollectionForMarketing) { const { metaMetricsId } = this.state; + this.store.updateState({ dataCollectionForMarketing }); + + if (!dataCollectionForMarketing && this.state.marketingCampaignCookieId) { + this.setMarketingCampaignCookieId(null); + } + return metaMetricsId; } + setMarketingCampaignCookieId(marketingCampaignCookieId) { + this.store.updateState({ marketingCampaignCookieId }); + } + get state() { return this.store.getState(); } @@ -704,6 +717,7 @@ export default class MetaMetricsController { userAgent: window.navigator.userAgent, page, referrer, + marketingCampaignCookieId: this.state.marketingCampaignCookieId, }; } diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 40a3f2795f01..de75170b8e58 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -18,6 +18,7 @@ const VERSION = '0.0.1-test'; const FAKE_CHAIN_ID = '0x1338'; const LOCALE = 'en_US'; const TEST_META_METRICS_ID = '0xabc'; +const TEST_GA_COOKIE_ID = '123456.123455'; const DUMMY_ACTION_ID = 'DUMMY_ACTION_ID'; const MOCK_EXTENSION_ID = 'testid'; @@ -51,6 +52,7 @@ const DEFAULT_TEST_CONTEXT = { page: METAMETRICS_BACKGROUND_PAGE_OBJECT, referrer: undefined, userAgent: window.navigator.userAgent, + marketingCampaignCookieId: null, }; const DEFAULT_SHARED_PROPERTIES = { @@ -114,6 +116,7 @@ const SAMPLE_NON_PERSISTED_EVENT = { function getMetaMetricsController({ participateInMetaMetrics = true, metaMetricsId = TEST_META_METRICS_ID, + marketingCampaignCookieId = null, preferencesStore = getMockPreferencesStore(), getCurrentChainId = () => FAKE_CHAIN_ID, onNetworkDidChange = () => { @@ -131,6 +134,7 @@ function getMetaMetricsController({ initState: { participateInMetaMetrics, metaMetricsId, + marketingCampaignCookieId, fragments: { testid: SAMPLE_PERSISTED_EVENT, testid2: SAMPLE_NON_PERSISTED_EVENT, @@ -161,6 +165,9 @@ describe('MetaMetricsController', function () { expect(metaMetricsController.state.metaMetricsId).toStrictEqual( TEST_META_METRICS_ID, ); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); expect(metaMetricsController.locale).toStrictEqual( LOCALE.replace('_', '-'), ); @@ -340,6 +347,21 @@ describe('MetaMetricsController', function () { TEST_META_METRICS_ID, ); }); + it('should nullify the marketingCampaignCookieId when participateInMetaMetrics is toggled off', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + await metaMetricsController.setParticipateInMetaMetrics(false); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); + }); }); describe('submitEvent', function () { @@ -1243,7 +1265,65 @@ describe('MetaMetricsController', function () { expect(Object.keys(segmentApiCalls).length === 0).toStrictEqual(true); }); }); - + describe('setMarketingCampaignCookieId', function () { + it('should update marketingCampaignCookieId in the context when cookieId is available', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + }); + metaMetricsController.setMarketingCampaignCookieId(TEST_GA_COOKIE_ID); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + const spy = jest.spyOn(segment, 'track'); + metaMetricsController.submitEvent( + { + event: 'Fake Event', + category: 'Unit Test', + properties: { + test: 1, + }, + }, + { isOptIn: true }, + ); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + { + event: 'Fake Event', + anonymousId: METAMETRICS_ANONYMOUS_ID, + context: { + ...DEFAULT_TEST_CONTEXT, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }, + properties: { + test: 1, + ...DEFAULT_EVENT_PROPERTIES, + }, + messageId: Utils.generateRandomId(), + timestamp: new Date(), + }, + spy.mock.calls[0][1], + ); + }); + }); + describe('setDataCollectionForMarketing', function () { + it('should nullify the marketingCampaignCookieId when Data collection for marketing is toggled off', async function () { + const metaMetricsController = getMetaMetricsController({ + participateInMetaMetrics: true, + metaMetricsId: TEST_META_METRICS_ID, + dataCollectionForMarketing: true, + marketingCampaignCookieId: TEST_GA_COOKIE_ID, + }); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(TEST_GA_COOKIE_ID); + await metaMetricsController.setDataCollectionForMarketing(false); + expect( + metaMetricsController.state.marketingCampaignCookieId, + ).toStrictEqual(null); + }); + }); afterEach(function () { // flush the queues manually after each test segment.flush(); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0e658195a50f..37e16fc27031 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -328,6 +328,7 @@ import { addDappTransaction, addTransaction } from './lib/transaction/util'; import { LatticeKeyringOffscreen } from './lib/offscreen-bridge/lattice-offscreen-keyring'; import PREINSTALLED_SNAPS from './snaps/preinstalled-snaps'; import { WeakRefObjectMap } from './lib/WeakRefObjectMap'; +import { METAMASK_COOKIE_HANDLER } from './constants/stream'; // Notification controllers import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; @@ -3197,6 +3198,10 @@ export default class MetamaskController extends EventEmitter { metaMetricsController.setDataCollectionForMarketing.bind( metaMetricsController, ), + setMarketingCampaignCookieId: + metaMetricsController.setMarketingCampaignCookieId.bind( + metaMetricsController, + ), setCurrentLocale: preferencesController.setCurrentLocale.bind( preferencesController, ), @@ -5114,6 +5119,42 @@ export default class MetamaskController extends EventEmitter { ); } + setUpCookieHandlerCommunication({ connectionStream }) { + const { + metaMetricsId, + dataCollectionForMarketing, + participateInMetaMetrics, + } = this.metaMetricsController.store.getState(); + + if ( + metaMetricsId && + dataCollectionForMarketing && + participateInMetaMetrics + ) { + // setup multiplexing + const mux = setupMultiplex(connectionStream); + const metamaskCookieHandlerStream = mux.createStream( + METAMASK_COOKIE_HANDLER, + ); + // set up postStream transport + metamaskCookieHandlerStream.on( + 'data', + createMetaRPCHandler( + { + getCookieFromMarketingPage: + this.getCookieFromMarketingPage.bind(this), + }, + metamaskCookieHandlerStream, + ), + ); + } + } + + getCookieFromMarketingPage(data) { + const { ga_client_id: cookieId } = data; + this.metaMetricsController.setMarketingCampaignCookieId(cookieId); + } + /** * Called when we detect a suspicious domain. Requests the browser redirects * to our anti-phishing page. diff --git a/app/scripts/streams/cookie-handler-stream.ts b/app/scripts/streams/cookie-handler-stream.ts new file mode 100644 index 000000000000..1b218e8d0cec --- /dev/null +++ b/app/scripts/streams/cookie-handler-stream.ts @@ -0,0 +1,193 @@ +import browser from 'webextension-polyfill'; +import { WindowPostMessageStream } from '@metamask/post-message-stream'; +import ObjectMultiplex from '@metamask/object-multiplex'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-expect-error types/readable-stream.d.ts does not get picked up by ts-node +import { pipeline } from 'readable-stream'; +import { Substream } from '@metamask/object-multiplex/dist/Substream'; +import PortStream from 'extension-port-stream'; +import { EXTENSION_MESSAGES } from '../../../shared/constants/app'; +import { COOKIE_ID_MARKETING_WHITELIST_ORIGINS } from '../constants/marketing-site-whitelist'; +import { checkForLastError } from '../../../shared/modules/browser-runtime.utils'; +import { + METAMASK_COOKIE_HANDLER, + CONTENT_SCRIPT, + LEGACY_PUBLIC_CONFIG, + METAMASK_PROVIDER, + PHISHING_SAFELIST, + LEGACY_PROVIDER, + PHISHING_STREAM, +} from '../constants/stream'; +import { logStreamDisconnectWarning } from './shared'; + +export const isDetectedCookieMarketingSite: boolean = + COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( + (origin) => origin === window.location.origin, + ); + +let cookieHandlerPageMux: ObjectMultiplex, + cookieHandlerPageChannel: Substream, + cookieHandlerExtPort: browser.Runtime.Port, + cookieHandlerExtStream: PortStream | null, + cookieHandlerMux: ObjectMultiplex, + cookieHandlerExtChannel: Substream; + +function setupCookieHandlerStreamsFromOrigin(origin: string): void { + const cookieHandlerPageStream = new WindowPostMessageStream({ + name: CONTENT_SCRIPT, + target: 'CookieHandlerPage', + targetWindow: window, + targetOrigin: origin, + }); + + // create and connect channel muxers + // so we can handle the channels individually + cookieHandlerPageMux = new ObjectMultiplex(); + cookieHandlerPageMux.setMaxListeners(25); + + pipeline( + cookieHandlerPageMux, + cookieHandlerPageStream, + cookieHandlerPageMux, + (err: Error) => + logStreamDisconnectWarning('MetaMask Inpage Multiplex', err), + ); + + cookieHandlerPageChannel = cookieHandlerPageMux.createStream( + METAMASK_COOKIE_HANDLER, + ); + cookieHandlerPageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerPageMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerPageMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerPageMux.ignoreStream(PHISHING_SAFELIST); + cookieHandlerPageMux.ignoreStream(PHISHING_STREAM); +} + +/** + * establishes a communication stream between the content script and background.js + */ +export const setupCookieHandlerExtStreams = (): void => { + cookieHandlerExtPort = browser.runtime.connect({ + name: CONTENT_SCRIPT, + }); + cookieHandlerExtStream = new PortStream(cookieHandlerExtPort); + + // create and connect channel muxers + // so we can handle the channels individually + cookieHandlerMux = new ObjectMultiplex(); + cookieHandlerMux.setMaxListeners(25); + + pipeline( + cookieHandlerMux, + cookieHandlerExtStream, + cookieHandlerMux, + (err: Error) => { + logStreamDisconnectWarning('MetaMask Background Multiplex', err); + window.postMessage( + { + target: 'CookieHandlerPage', + data: { + // this object gets passed to @metamask/object-multiplex + name: METAMASK_COOKIE_HANDLER, // the @metamask/object-multiplex channel name + data: { + jsonrpc: '2.0', + method: 'METAMASK_STREAM_FAILURE', + }, + }, + }, + window.location.origin, + ); + }, + ); + + // forward communication across inpage-background for these channels only + cookieHandlerExtChannel = cookieHandlerMux.createStream( + METAMASK_COOKIE_HANDLER, + ); + cookieHandlerMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerMux.ignoreStream(PHISHING_SAFELIST); + cookieHandlerMux.ignoreStream(PHISHING_STREAM); + pipeline( + cookieHandlerPageChannel, + cookieHandlerExtChannel, + cookieHandlerPageChannel, + (error: Error) => + console.debug( + `MetaMask: Muxed traffic for channel "${METAMASK_COOKIE_HANDLER}" failed.`, + error, + ), + ); + + cookieHandlerExtPort.onDisconnect.addListener( + // eslint-disable-next-line @typescript-eslint/no-use-before-define + onDisconnectDestroyCookieStreams, + ); +}; + +/** Destroys all of the cookie handler extension streams */ +const destroyCookieExtStreams = () => { + cookieHandlerPageChannel.removeAllListeners(); + + cookieHandlerMux.removeAllListeners(); + cookieHandlerMux.destroy(); + + cookieHandlerExtChannel.removeAllListeners(); + cookieHandlerExtChannel.destroy(); + + cookieHandlerExtStream = null; +}; + +/** + * This listener destroys the phishing extension streams when the extension port is disconnected, + * so that streams may be re-established later the phishing extension port is reconnected. + */ +const onDisconnectDestroyCookieStreams = () => { + const err = checkForLastError(); + + cookieHandlerExtPort.onDisconnect.removeListener( + onDisconnectDestroyCookieStreams, + ); + + destroyCookieExtStreams(); + + /** + * If an error is found, reset the streams. When running two or more dapps, resetting the service + * worker may cause the error, "Error: Could not establish connection. Receiving end does not + * exist.", due to a race-condition. The disconnect event may be called by runtime.connect which + * may cause issues. We suspect that this is a chromium bug as this event should only be called + * once the port and connections are ready. Delay time is arbitrary. + */ + if (err) { + console.warn(`${err} Resetting the phishing streams.`); + setTimeout(setupCookieHandlerExtStreams, 1000); + } +}; + +const onMessageSetUpCookieHandlerStreams = (msg: { + name: string; + origin: string; +}): Promise | undefined => { + if (msg.name === EXTENSION_MESSAGES.READY) { + if (!cookieHandlerExtStream) { + setupCookieHandlerExtStreams(); + } + return Promise.resolve( + `MetaMask: handled "${EXTENSION_MESSAGES.READY}" for phishing streams`, + ); + } + return undefined; +}; + +/** + * Initializes two-way communication streams between the browser extension and + * the cookie id submission page context. This function also creates an event listener to + * reset the streams if the service worker resets. + */ +export const initializeCookieHandlerSteam = (): void => { + const { origin } = window.location; + setupCookieHandlerStreamsFromOrigin(origin); + setupCookieHandlerExtStreams(); + browser.runtime.onMessage.addListener(onMessageSetUpCookieHandlerStreams); +}; diff --git a/app/scripts/streams/shared.ts b/app/scripts/streams/shared.ts new file mode 100644 index 000000000000..73dc6ec1d6e5 --- /dev/null +++ b/app/scripts/streams/shared.ts @@ -0,0 +1,15 @@ +/** + * Error handler for page to extension stream disconnections + * + * @param remoteLabel - Remote stream name + * @param error - Stream connection error + */ +export function logStreamDisconnectWarning( + remoteLabel: string, + error: Error, +): void { + console.debug( + `MetaMask: Content script lost connection to "${remoteLabel}".`, + error, + ); +} diff --git a/test/e2e/tests/metrics/marketing-cookieid-mock-page/index.html b/test/e2e/tests/metrics/marketing-cookieid-mock-page/index.html new file mode 100644 index 000000000000..9233e3bf6a34 --- /dev/null +++ b/test/e2e/tests/metrics/marketing-cookieid-mock-page/index.html @@ -0,0 +1,35 @@ + + + + Mock E2E Cookie Handler + + + +
Mock Page for E2E Cookie Handler
+