From 5ff58246b64fb6ee559fca2295be4683667542bf Mon Sep 17 00:00:00 2001 From: Danica Shen Date: Thu, 22 Aug 2024 04:01:11 +0100 Subject: [PATCH 01/12] feat(2916): implement cookie id metric events tracking for marketing --- app/scripts/background.js | 19 ++- .../constants/marketing-site-whitelist.ts | 11 ++ app/scripts/contentscript.js | 22 ++- app/scripts/metamask-controller.js | 38 +++++ app/scripts/streams/cookie-handler-stream.ts | 146 ++++++++++++++++++ app/scripts/streams/shared.ts | 15 ++ 6 files changed, 236 insertions(+), 15 deletions(-) create mode 100644 app/scripts/constants/marketing-site-whitelist.ts create mode 100644 app/scripts/streams/cookie-handler-stream.ts create mode 100644 app/scripts/streams/shared.ts diff --git a/app/scripts/background.js b/app/scripts/background.js index 45fcd0315c10..3bd4456b62a1 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,22 @@ 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 if ( + senderUrl && + COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( + (origin) => origin === senderUrl.origin, + ) + ) { + console.log('----inside----'); + const portStreamForCookieHandlerPage = + overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); + controller.setUpCookieHandlerCommunication({ + connectionStream: portStreamForCookieHandlerPage, }); } else { // this is triggered when a new tab is opened, or origin(url) is changed diff --git a/app/scripts/constants/marketing-site-whitelist.ts b/app/scripts/constants/marketing-site-whitelist.ts new file mode 100644 index 000000000000..16ba7cc37c6d --- /dev/null +++ b/app/scripts/constants/marketing-site-whitelist.ts @@ -0,0 +1,11 @@ +export const COOKIE_ID_MARKETING_WHITELIST = [ + 'https://metamask.io', + 'https://learn.metamask.io', + 'https://mmi-support.zendesk.com/hc/en-us', + 'https://community.metamask.io/', + 'https://support.metamask.io/', +]; + +// 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/contentscript.js b/app/scripts/contentscript.js index ab2483c9f9a3..31b666a49554 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -9,6 +9,11 @@ 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'; // contexts const CONTENT_SCRIPT = 'metamask-contentscript'; @@ -431,19 +436,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 +517,10 @@ const start = () => { return; } + if (isDetectedCookieMarketingSite) { + initializeCookieHandlerSteam(); + } + if (shouldInjectProvider()) { initStreams(); diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0e658195a50f..fa324f6edbde 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -360,6 +360,7 @@ export const METAMASK_CONTROLLER_EVENTS = { // stream channels const PHISHING_SAFELIST = 'metamask-phishing-safelist'; +const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; export default class MetamaskController extends EventEmitter { /** @@ -5114,6 +5115,43 @@ 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 + // TODO: Add handler here to modify trackEvents metrics context + // Ideally take ga_client_id here from page message + metamaskCookieHandlerStream.on( + 'data', + createMetaRPCHandler( + { + getCookieFromMarketingPage: getCookieFromMarketingPage.bind(this), + }, + metamaskCookieHandlerStream, + ), + ); + } + } + + getCookieFromMarketingPage(data) { + console.log('getCookieFromMarketingPage', data); + return data; + } + /** * 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..ce6c1f2325f6 --- /dev/null +++ b/app/scripts/streams/cookie-handler-stream.ts @@ -0,0 +1,146 @@ +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 { logStreamDisconnectWarning } from './shared'; +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'; + +const CONTENT_SCRIPT = 'metamask-contentscript'; +const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; + +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, + }); + + cookieHandlerPageStream.on('data', (data) => { + console.log('Received setupCookieHandlerStreamsFromOrigin:', data); + }); + + // 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, + ); + + cookieHandlerPageChannel.on('data', (data) => { + console.log('Received cookieHandlerPageChannel:', data); + }); +} + +/** + * establishes a communication stream between the content script and background.js + */ +export const setupCookieHandlerExtStreams = (origin): 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, + ); + pipeline( + cookieHandlerPageChannel, + cookieHandlerExtChannel, + cookieHandlerPageChannel, + (error: Error) => + console.debug( + `MetaMask: Muxed traffic for channel "${METAMASK_COOKIE_HANDLER}" failed.`, + error, + ), + ); + + cookieHandlerExtChannel.on('data', (data) => { + console.log('cookieHandlerExtChannel to content script:', data); + }); + + // eslint-disable-next-line @typescript-eslint/no-use-before-define + // cookieHandlerExtPort.onDisconnect.addListener(onDisconnectDestroyPhishingStreams); +}; + +const onMessageSetUpCookieHandlerStreams = (msg: { + name: string; + origin: string; +}): Promise | undefined => { + if (msg.name === EXTENSION_MESSAGES.READY) { + if (!cookieHandlerExtStream) { + setupCookieHandlerExtStreams(origin); + } + 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.origin; + setupCookieHandlerStreamsFromOrigin(origin); + setupCookieHandlerExtStreams(origin); + 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, + ); +} From b246ffa7c0d943fef0e6c953add1c3d7f13561cc Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:23:05 -0400 Subject: [PATCH 02/12] setting up cookie handler stream and updating metrics event context to hold cookie id --- app/scripts/background.js | 24 ++--- app/scripts/constants/sentry-state.ts | 1 + app/scripts/controllers/metametrics.js | 6 ++ app/scripts/controllers/metametrics.test.js | 4 + app/scripts/metamask-controller.js | 14 ++- app/scripts/streams/cookie-handler-stream.ts | 88 ++++++++++++++----- ...rs-after-init-opt-in-background-state.json | 1 + .../errors-after-init-opt-in-ui-state.json | 1 + 8 files changed, 101 insertions(+), 38 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 3bd4456b62a1..a563d0f81393 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -891,18 +891,6 @@ export function setupController( controller.setupPhishingCommunication({ connectionStream: portStreamForPhishingPage, }); - } else if ( - senderUrl && - COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( - (origin) => origin === senderUrl.origin, - ) - ) { - console.log('----inside----'); - const portStreamForCookieHandlerPage = - overrides?.getPortStream?.(remotePort) || new PortStream(remotePort); - controller.setUpCookieHandlerCommunication({ - connectionStream: portStreamForCookieHandlerPage, - }); } else { // this is triggered when a new tab is opened, or origin(url) is changed if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { @@ -921,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/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/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 8aafa0893d53..d598ddb04574 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: {}, @@ -481,6 +482,10 @@ export default class MetaMetricsController { return metaMetricsId; } + setMarketingCampaignCookieId(marketingCampaignCookieId) { + this.store.updateState({ marketingCampaignCookieId }); + } + get state() { return this.store.getState(); } @@ -704,6 +709,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..a5530bd10e3e 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: TEST_GA_COOKIE_ID, }; const DEFAULT_SHARED_PROPERTIES = { @@ -114,6 +116,7 @@ const SAMPLE_NON_PERSISTED_EVENT = { function getMetaMetricsController({ participateInMetaMetrics = true, metaMetricsId = TEST_META_METRICS_ID, + marketingCampaignCookieId = TEST_GA_COOKIE_ID, 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, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fa324f6edbde..c232009ae014 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 './streams/stream-constants'; // Notification controllers import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; @@ -360,7 +361,6 @@ export const METAMASK_CONTROLLER_EVENTS = { // stream channels const PHISHING_SAFELIST = 'metamask-phishing-safelist'; -const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; export default class MetamaskController extends EventEmitter { /** @@ -3198,6 +3198,10 @@ export default class MetamaskController extends EventEmitter { metaMetricsController.setDataCollectionForMarketing.bind( metaMetricsController, ), + setMarketingCampaignCookieId: + metaMetricsController.setMarketingCampaignCookieId.bind( + metaMetricsController, + ), setCurrentLocale: preferencesController.setCurrentLocale.bind( preferencesController, ), @@ -5127,6 +5131,7 @@ export default class MetamaskController extends EventEmitter { dataCollectionForMarketing && participateInMetaMetrics ) { + console.log('metrics enabled'); // setup multiplexing const mux = setupMultiplex(connectionStream); const metamaskCookieHandlerStream = mux.createStream( @@ -5139,7 +5144,8 @@ export default class MetamaskController extends EventEmitter { 'data', createMetaRPCHandler( { - getCookieFromMarketingPage: getCookieFromMarketingPage.bind(this), + getCookieFromMarketingPage: + this.getCookieFromMarketingPage.bind(this), }, metamaskCookieHandlerStream, ), @@ -5148,8 +5154,8 @@ export default class MetamaskController extends EventEmitter { } getCookieFromMarketingPage(data) { - console.log('getCookieFromMarketingPage', data); - return data; + const { ga_client_id: cookieId } = data; + this.metaMetricsController.setMarketingCampaignCookieId(cookieId); } /** diff --git a/app/scripts/streams/cookie-handler-stream.ts b/app/scripts/streams/cookie-handler-stream.ts index ce6c1f2325f6..38e7188a2747 100644 --- a/app/scripts/streams/cookie-handler-stream.ts +++ b/app/scripts/streams/cookie-handler-stream.ts @@ -4,14 +4,20 @@ 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 { logStreamDisconnectWarning } from './shared'; 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'; - -const CONTENT_SCRIPT = 'metamask-contentscript'; -const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; +import { checkForLastError } from '../../../shared/modules/browser-runtime.utils'; +import { + METAMASK_COOKIE_HANDLER, + CONTENT_SCRIPT, + LEGACY_PUBLIC_CONFIG, + METAMASK_PROVIDER, + PHISHING_SAFELIST, + LEGACY_PROVIDER, +} from './stream-constants'; +import { logStreamDisconnectWarning } from './shared'; export const isDetectedCookieMarketingSite: boolean = COOKIE_ID_MARKETING_WHITELIST_ORIGINS.some( @@ -33,10 +39,6 @@ function setupCookieHandlerStreamsFromOrigin(origin: string): void { targetOrigin: origin, }); - cookieHandlerPageStream.on('data', (data) => { - console.log('Received setupCookieHandlerStreamsFromOrigin:', data); - }); - // create and connect channel muxers // so we can handle the channels individually cookieHandlerPageMux = new ObjectMultiplex(); @@ -53,16 +55,16 @@ function setupCookieHandlerStreamsFromOrigin(origin: string): void { cookieHandlerPageChannel = cookieHandlerPageMux.createStream( METAMASK_COOKIE_HANDLER, ); - - cookieHandlerPageChannel.on('data', (data) => { - console.log('Received cookieHandlerPageChannel:', data); - }); + cookieHandlerPageMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerPageMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerPageMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerPageMux.ignoreStream(PHISHING_SAFELIST); } /** - * establishes a communication stream between the content script and background.js + * establishes a communication stream between the content script and background.js */ -export const setupCookieHandlerExtStreams = (origin): void => { +export const setupCookieHandlerExtStreams = (): void => { cookieHandlerExtPort = browser.runtime.connect({ name: CONTENT_SCRIPT, }); @@ -72,6 +74,7 @@ export const setupCookieHandlerExtStreams = (origin): void => { // so we can handle the channels individually cookieHandlerMux = new ObjectMultiplex(); cookieHandlerMux.setMaxListeners(25); + pipeline( cookieHandlerMux, cookieHandlerExtStream, @@ -99,6 +102,10 @@ export const setupCookieHandlerExtStreams = (origin): void => { cookieHandlerExtChannel = cookieHandlerMux.createStream( METAMASK_COOKIE_HANDLER, ); + cookieHandlerMux.ignoreStream(LEGACY_PUBLIC_CONFIG); + cookieHandlerMux.ignoreStream(LEGACY_PROVIDER); + cookieHandlerMux.ignoreStream(METAMASK_PROVIDER); + cookieHandlerMux.ignoreStream(PHISHING_SAFELIST); pipeline( cookieHandlerPageChannel, cookieHandlerExtChannel, @@ -110,12 +117,49 @@ export const setupCookieHandlerExtStreams = (origin): void => { ), ); - cookieHandlerExtChannel.on('data', (data) => { - console.log('cookieHandlerExtChannel to content script:', data); - }); + cookieHandlerExtPort.onDisconnect.addListener( + // eslint-disable-next-line @typescript-eslint/no-use-before-define + onDisconnectDestroyCookieStreams, + ); +}; - // eslint-disable-next-line @typescript-eslint/no-use-before-define - // cookieHandlerExtPort.onDisconnect.addListener(onDisconnectDestroyPhishingStreams); +/** 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: { @@ -124,7 +168,7 @@ const onMessageSetUpCookieHandlerStreams = (msg: { }): Promise | undefined => { if (msg.name === EXTENSION_MESSAGES.READY) { if (!cookieHandlerExtStream) { - setupCookieHandlerExtStreams(origin); + setupCookieHandlerExtStreams(); } return Promise.resolve( `MetaMask: handled "${EXTENSION_MESSAGES.READY}" for phishing streams`, @@ -139,8 +183,8 @@ const onMessageSetUpCookieHandlerStreams = (msg: { * reset the streams if the service worker resets. */ export const initializeCookieHandlerSteam = (): void => { - const origin = window.location.origin; + const { origin } = window.location; setupCookieHandlerStreamsFromOrigin(origin); - setupCookieHandlerExtStreams(origin); + setupCookieHandlerExtStreams(); browser.runtime.onMessage.addListener(onMessageSetUpCookieHandlerStreams); }; diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 7691ef634b5e..c8b74e873e7e 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -117,6 +117,7 @@ "participateInMetaMetrics": true, "metaMetricsId": "fake-metrics-id", "dataCollectionForMarketing": "boolean", + "marketingCampaignCookieId": null, "eventsBeforeMetricsOptIn": "object", "traits": "object", "previousUserTraits": "object", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 14c0b1712b19..4124e34a6752 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -127,6 +127,7 @@ "useExternalServices": "boolean", "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", + "marketingCampaignCookieId": null, "eventsBeforeMetricsOptIn": "object", "traits": "object", "previousUserTraits": "object", From f37f4dbdbf8e13ad872fc4de8b9ea5211e4efa66 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:23:32 -0400 Subject: [PATCH 03/12] extracting constants --- app/scripts/streams/stream-constants.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 app/scripts/streams/stream-constants.ts diff --git a/app/scripts/streams/stream-constants.ts b/app/scripts/streams/stream-constants.ts new file mode 100644 index 000000000000..46dade37d20b --- /dev/null +++ b/app/scripts/streams/stream-constants.ts @@ -0,0 +1,9 @@ +// contexts +export const CONTENT_SCRIPT = 'metamask-contentscript'; + +// stream channels +export const LEGACY_PROVIDER = 'provider'; +export const LEGACY_PUBLIC_CONFIG = 'publicConfig'; +export const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; +export const METAMASK_PROVIDER = 'metamask-provider'; +export const PHISHING_SAFELIST = 'metamask-phishing-safelist'; From 547f1d0ace6f2dd5e2cba4365b70ba7b90a664cf Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:55:36 -0400 Subject: [PATCH 04/12] adding e2e test and updating the constant file --- .../stream-constant.ts} | 11 +- app/scripts/metamask-controller.js | 3 +- app/scripts/streams/cookie-handler-stream.ts | 2 +- .../metrics/attribution/mock-page/index.html | 33 ++++++ .../metrics/attribution/send-cookieid.spec.ts | 106 ++++++++++++++++++ 5 files changed, 149 insertions(+), 6 deletions(-) rename app/scripts/{streams/stream-constants.ts => constants/stream-constant.ts} (67%) create mode 100644 test/e2e/tests/metrics/attribution/mock-page/index.html create mode 100644 test/e2e/tests/metrics/attribution/send-cookieid.spec.ts diff --git a/app/scripts/streams/stream-constants.ts b/app/scripts/constants/stream-constant.ts similarity index 67% rename from app/scripts/streams/stream-constants.ts rename to app/scripts/constants/stream-constant.ts index 46dade37d20b..594f7c31a240 100644 --- a/app/scripts/streams/stream-constants.ts +++ b/app/scripts/constants/stream-constant.ts @@ -1,9 +1,14 @@ // contexts export const CONTENT_SCRIPT = 'metamask-contentscript'; +export const METAMASK_INPAGE = 'metamask-inpage'; // stream channels -export const LEGACY_PROVIDER = 'provider'; -export const LEGACY_PUBLIC_CONFIG = 'publicConfig'; -export const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; export const METAMASK_PROVIDER = 'metamask-provider'; +export const METAMASK_COOKIE_HANDLER = 'metamask-cookie-handler'; export const PHISHING_SAFELIST = 'metamask-phishing-safelist'; + +// 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/metamask-controller.js b/app/scripts/metamask-controller.js index c232009ae014..f9a4df2dd30e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -328,7 +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 './streams/stream-constants'; +import { METAMASK_COOKIE_HANDLER } from './constants/stream-constant'; // Notification controllers import { createTxVerificationMiddleware } from './lib/tx-verification/tx-verification-middleware'; @@ -5131,7 +5131,6 @@ export default class MetamaskController extends EventEmitter { dataCollectionForMarketing && participateInMetaMetrics ) { - console.log('metrics enabled'); // setup multiplexing const mux = setupMultiplex(connectionStream); const metamaskCookieHandlerStream = mux.createStream( diff --git a/app/scripts/streams/cookie-handler-stream.ts b/app/scripts/streams/cookie-handler-stream.ts index 38e7188a2747..b0679e12e3de 100644 --- a/app/scripts/streams/cookie-handler-stream.ts +++ b/app/scripts/streams/cookie-handler-stream.ts @@ -16,7 +16,7 @@ import { METAMASK_PROVIDER, PHISHING_SAFELIST, LEGACY_PROVIDER, -} from './stream-constants'; +} from '../constants/stream-constant'; import { logStreamDisconnectWarning } from './shared'; export const isDetectedCookieMarketingSite: boolean = diff --git a/test/e2e/tests/metrics/attribution/mock-page/index.html b/test/e2e/tests/metrics/attribution/mock-page/index.html new file mode 100644 index 000000000000..bbd619ff52cf --- /dev/null +++ b/test/e2e/tests/metrics/attribution/mock-page/index.html @@ -0,0 +1,33 @@ + + + + Mock E2E Cookie Handler + + + +
Hello
+