mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Fix Provider Tracking Metrics (#15082)
This commit is contained in:
parent
0622883a3c
commit
df646a03eb
@ -1,19 +1,91 @@
|
|||||||
|
import { MESSAGE_TYPE, ORIGIN_METAMASK } from '../../../shared/constants/app';
|
||||||
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
import { SECOND } from '../../../shared/constants/time';
|
import { SECOND } from '../../../shared/constants/time';
|
||||||
|
|
||||||
const USER_PROMPTED_EVENT_NAME_MAP = {
|
/**
|
||||||
eth_signTypedData_v4: EVENT_NAMES.SIGNATURE_REQUESTED,
|
* These types determine how the method tracking middleware handles incoming
|
||||||
eth_signTypedData_v3: EVENT_NAMES.SIGNATURE_REQUESTED,
|
* requests based on the method name. There are three options right now but
|
||||||
eth_signTypedData: EVENT_NAMES.SIGNATURE_REQUESTED,
|
* the types could be expanded to cover other options in the future.
|
||||||
eth_personal_sign: EVENT_NAMES.SIGNATURE_REQUESTED,
|
*/
|
||||||
eth_sign: EVENT_NAMES.SIGNATURE_REQUESTED,
|
const RATE_LIMIT_TYPES = {
|
||||||
eth_getEncryptionPublicKey: EVENT_NAMES.ENCRYPTION_PUBLIC_KEY_REQUESTED,
|
RATE_LIMITED: 'rate_limited',
|
||||||
eth_decrypt: EVENT_NAMES.DECRYPTION_REQUESTED,
|
BLOCKED: 'blocked',
|
||||||
wallet_requestPermissions: EVENT_NAMES.PERMISSIONS_REQUESTED,
|
NON_RATE_LIMITED: 'non_rate_limited',
|
||||||
eth_requestAccounts: EVENT_NAMES.PERMISSIONS_REQUESTED,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const samplingTimeouts = {};
|
/**
|
||||||
|
* This object maps a method name to a RATE_LIMIT_TYPE. If not in this map the
|
||||||
|
* default is 'RATE_LIMITED'
|
||||||
|
*/
|
||||||
|
const RATE_LIMIT_MAP = {
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.PERSONAL_SIGN]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_DECRYPT]: RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY]:
|
||||||
|
RATE_LIMIT_TYPES.NON_RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS]: RATE_LIMIT_TYPES.RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.WALLET_REQUEST_PERMISSIONS]: RATE_LIMIT_TYPES.RATE_LIMITED,
|
||||||
|
[MESSAGE_TYPE.SEND_METADATA]: RATE_LIMIT_TYPES.BLOCKED,
|
||||||
|
[MESSAGE_TYPE.GET_PROVIDER_STATE]: RATE_LIMIT_TYPES.BLOCKED,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For events with user interaction (approve / reject | cancel) this map will
|
||||||
|
* return an object with APPROVED, REJECTED and REQUESTED keys that map to the
|
||||||
|
* appropriate event names.
|
||||||
|
*/
|
||||||
|
const EVENT_NAME_MAP = {
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN]: {
|
||||||
|
APPROVED: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA]: {
|
||||||
|
APPROVED: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3]: {
|
||||||
|
APPROVED: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4]: {
|
||||||
|
APPROVED: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.PERSONAL_SIGN]: {
|
||||||
|
APPROVED: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_DECRYPT]: {
|
||||||
|
APPROVED: EVENT_NAMES.DECRYPTION_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.DECRYPTION_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.DECRYPTION_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY]: {
|
||||||
|
APPROVED: EVENT_NAMES.ENCRYPTION_PUBLIC_KEY_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.ENCRYPTION_PUBLIC_KEY_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.ENCRYPTION_PUBLIC_KEY_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS]: {
|
||||||
|
APPROVED: EVENT_NAMES.PERMISSIONS_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.PERMISSIONS_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.PERMISSIONS_REQUESTED,
|
||||||
|
},
|
||||||
|
[MESSAGE_TYPE.WALLET_REQUEST_PERMISSIONS]: {
|
||||||
|
APPROVED: EVENT_NAMES.PERMISSIONS_APPROVED,
|
||||||
|
REJECTED: EVENT_NAMES.PERMISSIONS_REJECTED,
|
||||||
|
REQUESTED: EVENT_NAMES.PERMISSIONS_REQUESTED,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const rateLimitTimeouts = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a middleware that tracks inpage_provider usage using sampling for
|
* Returns a middleware that tracks inpage_provider usage using sampling for
|
||||||
@ -21,65 +93,113 @@ const samplingTimeouts = {};
|
|||||||
* signature requests
|
* signature requests
|
||||||
*
|
*
|
||||||
* @param {object} opts - options for the rpc method tracking middleware
|
* @param {object} opts - options for the rpc method tracking middleware
|
||||||
* @param {Function} opts.trackEvent - trackEvent method from MetaMetricsController
|
* @param {Function} opts.trackEvent - trackEvent method from
|
||||||
* @param {Function} opts.getMetricsState - get the state of MetaMetricsController
|
* MetaMetricsController
|
||||||
|
* @param {Function} opts.getMetricsState - get the state of
|
||||||
|
* MetaMetricsController
|
||||||
|
* @param {number} [opts.rateLimitSeconds] - number of seconds to wait before
|
||||||
|
* allowing another set of events to be tracked.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export default function createRPCMethodTrackingMiddleware({
|
export default function createRPCMethodTrackingMiddleware({
|
||||||
trackEvent,
|
trackEvent,
|
||||||
getMetricsState,
|
getMetricsState,
|
||||||
|
rateLimitSeconds = 60,
|
||||||
}) {
|
}) {
|
||||||
return function rpcMethodTrackingMiddleware(
|
return function rpcMethodTrackingMiddleware(
|
||||||
/** @type {any} */ req,
|
/** @type {any} */ req,
|
||||||
/** @type {any} */ res,
|
/** @type {any} */ res,
|
||||||
/** @type {Function} */ next,
|
/** @type {Function} */ next,
|
||||||
) {
|
) {
|
||||||
const startTime = Date.now();
|
const { origin, method } = req;
|
||||||
const { origin } = req;
|
|
||||||
|
// Determine what type of rate limit to apply based on method
|
||||||
|
const rateLimitType =
|
||||||
|
RATE_LIMIT_MAP[method] ?? RATE_LIMIT_TYPES.RATE_LIMITED;
|
||||||
|
|
||||||
|
// If the rateLimitType is RATE_LIMITED check the rateLimitTimeouts
|
||||||
|
const rateLimited =
|
||||||
|
rateLimitType === RATE_LIMIT_TYPES.RATE_LIMITED &&
|
||||||
|
typeof rateLimitTimeouts[method] !== 'undefined';
|
||||||
|
|
||||||
|
// Get the participateInMetaMetrics state to determine if we should track
|
||||||
|
// anything. This is extra redundancy because this value is checked in
|
||||||
|
// the metametrics controller's trackEvent method as well.
|
||||||
|
const userParticipatingInMetaMetrics =
|
||||||
|
getMetricsState().participateInMetaMetrics === true;
|
||||||
|
|
||||||
|
// Get the event type, each of which has APPROVED, REJECTED and REQUESTED
|
||||||
|
// keys for the various events in the flow.
|
||||||
|
const eventType = EVENT_NAME_MAP[method];
|
||||||
|
|
||||||
|
// Boolean variable that reduces code duplication and increases legibility
|
||||||
|
const shouldTrackEvent =
|
||||||
|
// Don't track if the request came from our own UI or background
|
||||||
|
origin !== ORIGIN_METAMASK &&
|
||||||
|
// Don't track if this is a blocked method
|
||||||
|
rateLimitType !== RATE_LIMIT_TYPES.BLOCKED &&
|
||||||
|
// Don't track if the rate limit has been hit
|
||||||
|
rateLimited === false &&
|
||||||
|
// Don't track if the user isn't participating in metametrics
|
||||||
|
userParticipatingInMetaMetrics === true;
|
||||||
|
|
||||||
|
if (shouldTrackEvent) {
|
||||||
|
// We track an initial "requested" event as soon as the dapp calls the
|
||||||
|
// provider method. For the events not special cased this is the only
|
||||||
|
// event that will be fired and the event name will be
|
||||||
|
// 'Provider Method Called'.
|
||||||
|
const event = eventType
|
||||||
|
? eventType.REQUESTED
|
||||||
|
: EVENT_NAMES.PROVIDER_METHOD_CALLED;
|
||||||
|
|
||||||
|
const properties = {};
|
||||||
|
|
||||||
|
if (event === EVENT_NAMES.SIGNATURE_REQUESTED) {
|
||||||
|
properties.signature_type = method;
|
||||||
|
} else {
|
||||||
|
properties.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent({
|
||||||
|
event,
|
||||||
|
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
|
||||||
|
referrer: {
|
||||||
|
url: origin,
|
||||||
|
},
|
||||||
|
properties,
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimitTimeouts[method] = setTimeout(() => {
|
||||||
|
delete rateLimitTimeouts[method];
|
||||||
|
}, SECOND * rateLimitSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
next((callback) => {
|
next((callback) => {
|
||||||
const endTime = Date.now();
|
if (shouldTrackEvent === false || typeof eventType === 'undefined') {
|
||||||
if (!getMetricsState().participateInMetaMetrics) {
|
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
if (USER_PROMPTED_EVENT_NAME_MAP[req.method]) {
|
|
||||||
const userRejected = res.error?.code === 4001;
|
// An error code of 4001 means the user rejected the request, which we
|
||||||
trackEvent({
|
// can use here to determine which event to track.
|
||||||
event: USER_PROMPTED_EVENT_NAME_MAP[req.method],
|
const event =
|
||||||
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
|
res.error?.code === 4001 ? eventType.REJECTED : eventType.APPROVED;
|
||||||
referrer: {
|
|
||||||
url: origin,
|
const properties = {};
|
||||||
},
|
|
||||||
properties: {
|
if (eventType.REQUESTED === EVENT_NAMES.SIGNATURE_REQUESTED) {
|
||||||
method: req.method,
|
properties.signature_type = method;
|
||||||
status: userRejected ? 'rejected' : 'approved',
|
} else {
|
||||||
error_code: res.error?.code,
|
properties.method = method;
|
||||||
error_message: res.error?.message,
|
|
||||||
has_result: typeof res.result !== 'undefined',
|
|
||||||
duration: endTime - startTime,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (typeof samplingTimeouts[req.method] === 'undefined') {
|
|
||||||
trackEvent({
|
|
||||||
event: 'Provider Method Called',
|
|
||||||
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
|
|
||||||
referrer: {
|
|
||||||
url: origin,
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
method: req.method,
|
|
||||||
error_code: res.error?.code,
|
|
||||||
error_message: res.error?.message,
|
|
||||||
has_result: typeof res.result !== 'undefined',
|
|
||||||
duration: endTime - startTime,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Only record one call to this method every ten seconds to avoid
|
|
||||||
// overloading network requests.
|
|
||||||
samplingTimeouts[req.method] = setTimeout(() => {
|
|
||||||
delete samplingTimeouts[req.method];
|
|
||||||
}, SECOND * 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackEvent({
|
||||||
|
event,
|
||||||
|
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
|
||||||
|
referrer: {
|
||||||
|
url: origin,
|
||||||
|
},
|
||||||
|
properties,
|
||||||
|
});
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
217
app/scripts/lib/createRPCMethodTrackingMiddleware.test.js
Normal file
217
app/scripts/lib/createRPCMethodTrackingMiddleware.test.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
||||||
|
import { EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
|
import { SECOND } from '../../../shared/constants/time';
|
||||||
|
import createRPCMethodTrackingMiddleware from './createRPCMethodTrackingMiddleware';
|
||||||
|
|
||||||
|
const trackEvent = jest.fn();
|
||||||
|
const metricsState = { participateInMetaMetrics: null };
|
||||||
|
const getMetricsState = () => metricsState;
|
||||||
|
|
||||||
|
const handler = createRPCMethodTrackingMiddleware({
|
||||||
|
trackEvent,
|
||||||
|
getMetricsState,
|
||||||
|
rateLimitSeconds: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
function getNext(timeout = 500) {
|
||||||
|
let deferred;
|
||||||
|
const promise = new Promise((resolve) => {
|
||||||
|
deferred = {
|
||||||
|
resolve,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const cb = () => deferred.resolve();
|
||||||
|
let triggerNext;
|
||||||
|
setTimeout(() => {
|
||||||
|
deferred.resolve();
|
||||||
|
}, timeout);
|
||||||
|
return {
|
||||||
|
executeMiddlewareStack: async () => {
|
||||||
|
if (triggerNext) {
|
||||||
|
triggerNext(() => cb());
|
||||||
|
}
|
||||||
|
return await deferred.resolve();
|
||||||
|
},
|
||||||
|
promise,
|
||||||
|
next: (postReqHandler) => {
|
||||||
|
triggerNext = postReqHandler;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForSeconds = async (seconds) =>
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, SECOND * seconds));
|
||||||
|
|
||||||
|
describe('createRPCMethodTrackingMiddleware', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
metricsState.participateInMetaMetrics = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('before participateInMetaMetrics is set', () => {
|
||||||
|
it('should not track an event for a signature request', async () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.ETH_SIGN,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const { executeMiddlewareStack, next } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
expect(trackEvent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('participateInMetaMetrics is set to false', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
metricsState.participateInMetaMetrics = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not track an event for a signature request', async () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.ETH_SIGN,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const { executeMiddlewareStack, next } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
expect(trackEvent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('participateInMetaMetrics is set to true', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
metricsState.participateInMetaMetrics = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should immediately track a ${EVENT_NAMES.SIGNATURE_REQUESTED} event`, () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.ETH_SIGN,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const { next } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
expect(trackEvent).toHaveBeenCalledTimes(1);
|
||||||
|
expect(trackEvent.mock.calls[0][0]).toMatchObject({
|
||||||
|
category: 'inpage_provider',
|
||||||
|
event: EVENT_NAMES.SIGNATURE_REQUESTED,
|
||||||
|
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN },
|
||||||
|
referrer: { url: 'some.dapp' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should track a ${EVENT_NAMES.SIGNATURE_APPROVED} event if the user approves`, async () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const { next, executeMiddlewareStack } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
||||||
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
||||||
|
category: 'inpage_provider',
|
||||||
|
event: EVENT_NAMES.SIGNATURE_APPROVED,
|
||||||
|
properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4 },
|
||||||
|
referrer: { url: 'some.dapp' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should track a ${EVENT_NAMES.SIGNATURE_REJECTED} event if the user approves`, async () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.PERSONAL_SIGN,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: { code: 4001 },
|
||||||
|
};
|
||||||
|
const { next, executeMiddlewareStack } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
||||||
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
||||||
|
category: 'inpage_provider',
|
||||||
|
event: EVENT_NAMES.SIGNATURE_REJECTED,
|
||||||
|
properties: { signature_type: MESSAGE_TYPE.PERSONAL_SIGN },
|
||||||
|
referrer: { url: 'some.dapp' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should track a ${EVENT_NAMES.PERMISSIONS_APPROVED} event if the user approves`, async () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS,
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {};
|
||||||
|
const { next, executeMiddlewareStack } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
||||||
|
expect(trackEvent.mock.calls[1][0]).toMatchObject({
|
||||||
|
category: 'inpage_provider',
|
||||||
|
event: EVENT_NAMES.PERMISSIONS_APPROVED,
|
||||||
|
properties: { method: MESSAGE_TYPE.ETH_REQUEST_ACCOUNTS },
|
||||||
|
referrer: { url: 'some.dapp' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should never track blocked methods such as ${MESSAGE_TYPE.GET_PROVIDER_STATE}`, () => {
|
||||||
|
const req = {
|
||||||
|
method: MESSAGE_TYPE.GET_PROVIDER_STATE,
|
||||||
|
origin: 'www.notadapp.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const { next, executeMiddlewareStack } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
expect(trackEvent).not.toHaveBeenCalled();
|
||||||
|
executeMiddlewareStack();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should only track events when not rate limited`, async () => {
|
||||||
|
const req = {
|
||||||
|
method: 'eth_chainId',
|
||||||
|
origin: 'some.dapp',
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
|
||||||
|
while (callCount < 3) {
|
||||||
|
callCount += 1;
|
||||||
|
const { next, executeMiddlewareStack } = getNext();
|
||||||
|
handler(req, res, next);
|
||||||
|
await executeMiddlewareStack();
|
||||||
|
if (callCount !== 3) {
|
||||||
|
await waitForSeconds(0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(trackEvent).toHaveBeenCalledTimes(2);
|
||||||
|
expect(trackEvent.mock.calls[0][0].properties.method).toBe('eth_chainId');
|
||||||
|
expect(trackEvent.mock.calls[1][0].properties.method).toBe('eth_chainId');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
||||||
|
'<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.js',
|
||||||
'<rootDir>/shared/**/*.js',
|
'<rootDir>/shared/**/*.js',
|
||||||
'<rootDir>/ui/**/*.js',
|
'<rootDir>/ui/**/*.js',
|
||||||
],
|
],
|
||||||
@ -20,6 +21,12 @@ module.exports = {
|
|||||||
lines: 100,
|
lines: 100,
|
||||||
statements: 100,
|
statements: 100,
|
||||||
},
|
},
|
||||||
|
'./app/scripts/lib/createRPCMethodTrackingMiddleware.js': {
|
||||||
|
branches: 95.65,
|
||||||
|
functions: 100,
|
||||||
|
lines: 100,
|
||||||
|
statements: 100,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// TODO: enable resetMocks
|
// TODO: enable resetMocks
|
||||||
// resetMocks: true,
|
// resetMocks: true,
|
||||||
@ -34,6 +41,7 @@ module.exports = {
|
|||||||
'<rootDir>/app/scripts/platforms/*.test.js',
|
'<rootDir>/app/scripts/platforms/*.test.js',
|
||||||
'<rootDir>app/scripts/controllers/network/**/*.test.js',
|
'<rootDir>app/scripts/controllers/network/**/*.test.js',
|
||||||
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
||||||
|
'<rootDir>/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js',
|
||||||
],
|
],
|
||||||
testTimeout: 2500,
|
testTimeout: 2500,
|
||||||
transform: {
|
transform: {
|
||||||
|
@ -39,11 +39,14 @@ export const MESSAGE_TYPE = {
|
|||||||
ETH_REQUEST_ACCOUNTS: 'eth_requestAccounts',
|
ETH_REQUEST_ACCOUNTS: 'eth_requestAccounts',
|
||||||
ETH_SIGN: 'eth_sign',
|
ETH_SIGN: 'eth_sign',
|
||||||
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
||||||
|
ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3',
|
||||||
|
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
|
||||||
GET_PROVIDER_STATE: 'metamask_getProviderState',
|
GET_PROVIDER_STATE: 'metamask_getProviderState',
|
||||||
LOG_WEB3_SHIM_USAGE: 'metamask_logWeb3ShimUsage',
|
LOG_WEB3_SHIM_USAGE: 'metamask_logWeb3ShimUsage',
|
||||||
PERSONAL_SIGN: 'personal_sign',
|
PERSONAL_SIGN: 'personal_sign',
|
||||||
SEND_METADATA: 'metamask_sendDomainMetadata',
|
SEND_METADATA: 'metamask_sendDomainMetadata',
|
||||||
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
|
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
|
||||||
|
WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions',
|
||||||
WATCH_ASSET: 'wallet_watchAsset',
|
WATCH_ASSET: 'wallet_watchAsset',
|
||||||
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
|
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
|
||||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||||
|
@ -276,9 +276,18 @@ export const REJECT_NOTFICIATION_CLOSE_SIG =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const EVENT_NAMES = {
|
export const EVENT_NAMES = {
|
||||||
|
ENCRYPTION_PUBLIC_KEY_APPROVED: 'Encryption Public Key Approved',
|
||||||
|
ENCRYPTION_PUBLIC_KEY_REJECTED: 'Encryption Public Key Rejected',
|
||||||
ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested',
|
ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested',
|
||||||
|
DECRYPTION_APPROVED: 'Decryption Approved',
|
||||||
|
DECRYPTION_REJECTED: 'Decryption Rejected',
|
||||||
DECRYPTION_REQUESTED: 'Decryption Requested',
|
DECRYPTION_REQUESTED: 'Decryption Requested',
|
||||||
|
PERMISSIONS_APPROVED: 'Permissions Approved',
|
||||||
|
PERMISSIONS_REJECTED: 'Permissions Rejected',
|
||||||
PERMISSIONS_REQUESTED: 'Permissions Requested',
|
PERMISSIONS_REQUESTED: 'Permissions Requested',
|
||||||
|
PROVIDER_METHOD_CALLED: 'Provider Method Called',
|
||||||
|
SIGNATURE_APPROVED: 'Signature Approved',
|
||||||
|
SIGNATURE_REJECTED: 'Signature Rejected',
|
||||||
SIGNATURE_REQUESTED: 'Signature Requested',
|
SIGNATURE_REQUESTED: 'Signature Requested',
|
||||||
TOKEN_ADDED: 'Token Added',
|
TOKEN_ADDED: 'Token Added',
|
||||||
TOKEN_DETECTED: 'Token Detected',
|
TOKEN_DETECTED: 'Token Detected',
|
||||||
|
Loading…
Reference in New Issue
Block a user