Skip to content

Commit

Permalink
Add deprecation warning for encryption methods
Browse files Browse the repository at this point in the history
A deprecation warning has been added for each of the two encryption-
related methods. The deprecation warning has been implemented as
middleware and included in both external providers (the inpage provider
and the external extension provider).
  • Loading branch information
Gudahtt committed Jul 26, 2022
1 parent 274c559 commit 19bc09f
Show file tree
Hide file tree
Showing 7 changed files with 496 additions and 5 deletions.
14 changes: 9 additions & 5 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@ module.exports = {
coveragePathIgnorePatterns: ['/node_modules/', '/mocks/', '/test/'],
coverageThreshold: {
global: {
branches: 56.19,
functions: 53.93,
lines: 59.01,
statements: 59.29,
branches: 58.12,
functions: 54.95,
lines: 60.76,
statements: 60.99,
},
},
projects: [
{
...baseConfig,
displayName: 'StreamProvider',
testEnvironment: 'node',
testMatch: ['**/StreamProvider.test.ts', '**/utils.test.ts'],
testMatch: [
'**/StreamProvider.test.ts',
'**/utils.test.ts',
'**/middleware/*.test.ts',
],
},
{
...baseConfig,
Expand Down
114 changes: 114 additions & 0 deletions src/MetaMaskInpageProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,120 @@ describe('MetaMaskInpageProvider: RPC', () => {
expect(provider.networkVersion).toBe('1');
});
});

describe('warnings', () => {
describe('rpc methods', () => {
const warnings = [
{
method: 'eth_decrypt',
warning: messages.warnings.rpc.ethDecryptDeprecation,
},
{
method: 'eth_getEncryptionPublicKey',
warning: messages.warnings.rpc.ethGetEncryptionPublicKeyDeprecation,
},
];

for (const { method, warning } of warnings) {
describe(method, () => {
it('should warn the first time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const [provider, connectionStream] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
connectionStream.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: null,
});
},
},
],
});

await provider.request({ method });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

it('should not warn the second time the method is called', async () => {
const [provider, connectionStream] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
connectionStream.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: null,
});
},
},
],
});
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');

await provider.request({ method });
await provider.request({ method });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

it('should allow the method to succeed', async () => {
const [provider, connectionStream] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
connectionStream.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: 'success!',
});
},
},
],
});

const response = await provider.request({ method });
expect(response).toBe('success!');
});

it('should allow the method to fail', async () => {
const [provider, connectionStream] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
connectionStream.reply('metamask-provider', {
id,
jsonrpc: '2.0',
error: { code: 0, message: 'failure!' },
});
},
},
],
});

await expect(() =>
provider.request({ method }),
).rejects.toMatchObject({
code: 0,
message: 'failure!',
});
});
});
}
});
});
});

describe('MetaMaskInpageProvider: Miscellanea', () => {
Expand Down
203 changes: 203 additions & 0 deletions src/extension-provider/createExternalExtensionProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,97 @@
import type { JsonRpcRequest } from 'json-rpc-engine';
import type { BaseProvider } from '../BaseProvider';
import { StreamProvider } from '../StreamProvider';
import { MockPort } from '../../test/mocks/MockPort';
import messages from '../messages';
import { getDeconstructedPromise } from '../../test/get-deconstructed-promise';
import { createExternalExtensionProvider } from './createExternalExtensionProvider';

/**
* The `createExternalExtensionProvider` function initializes the wallet state
* asynchronously without blocking on it. This helper function
* returns a provider initialized with the specified values.
*
* @param options - Options bag.
* @param options.initialState - The initial provider state returned on
* initialization. See {@link MetaMaskInpageProvider._initializeState}.
* @param options.onMethodCalled - A set of configuration objects for adding
* method-specific callbacks.
* @param options.onMethodCalled[].substream - The substream of the method that
* the callback is for.
* @param options.onMethodCalled[].method - The name of the method that the
* callback is for.
* @param options.onMethodCalled[].callback - The method callback.
* @returns A tuple of the initialized provider, the mock port used, and an
* "onWrite" stub that can be used to inspect message sent by the provider.
*/
async function getInitializedProvider({
initialState: {
accounts = [],
chainId = '0x0',
isUnlocked = true,
networkVersion = '0',
} = {},
onMethodCalled = [],
}: {
initialState?: Partial<Parameters<BaseProvider['_initializeState']>[0]>;
onMethodCalled?: {
substream: string;
method: string;
callback: (data: JsonRpcRequest<unknown>) => void;
}[];
} = {}) {
let isInitialized = false;
const onWrite = jest.fn();
const mockPort = new MockPort((name, data) => {
if (!isInitialized) {
if (
name === 'metamask-provider' &&
data.method === 'metamask_getProviderState'
) {
isInitialized = true;
// Wrap in `setImmediate` to ensure a reply is recieved by the provider
// after the provider has processed the request, to ensure that the
// provider recognizes the id.
setImmediate(() =>
mockPort.reply('metamask-provider', {
id: onWrite.mock.calls[0][1].id,
jsonrpc: '2.0',
result: {
accounts,
chainId,
isUnlocked,
networkVersion,
},
}),
);
} else {
// Ignore other messages before initialization
return;
}
}
for (const { substream, method, callback } of onMethodCalled) {
if (name === substream && data.method === method) {
// Wrap in `setImmediate` to ensure a reply is recieved by the provider
// after the provider has processed the request, to ensure that the
// provider recognizes the id.
setImmediate(() => callback(data));
}
}
onWrite(name, data);
});
(global.chrome.runtime.connect as any).mockImplementation(() => {
return mockPort;
});

const provider = createExternalExtensionProvider();
const { resolve: resolveInitialized, promise: waitForInitialization } =
getDeconstructedPromise();
provider.on('_initialized', resolveInitialized);
await waitForInitialization;

return [provider, mockPort, onWrite] as const;
}

describe('createExternalExtensionProvider', () => {
it('can be called and not throw', () => {
// `global.chrome.runtime` mock setup by `jest-chrome` in `jest.setup.js`
Expand All @@ -28,4 +118,117 @@ describe('createExternalExtensionProvider', () => {
const results = createExternalExtensionProvider();
expect(results).toBeInstanceOf(StreamProvider);
});

describe('RPC warnings', () => {
const warnings = [
{
method: 'eth_decrypt',
warning: messages.warnings.rpc.ethDecryptDeprecation,
},
{
method: 'eth_getEncryptionPublicKey',
warning: messages.warnings.rpc.ethGetEncryptionPublicKeyDeprecation,
},
];

for (const { method, warning } of warnings) {
describe(method, () => {
it('should warn the first time the method is called', async () => {
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');
const [provider, mockPort] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
mockPort.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: null,
});
},
},
],
});

await provider.request({ method });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

it('should not warn the second time the method is called', async () => {
const [provider, mockPort] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
mockPort.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: null,
});
},
},
],
});
const consoleWarnSpy = jest.spyOn(globalThis.console, 'warn');

await provider.request({ method });
await provider.request({ method });

expect(consoleWarnSpy).toHaveBeenCalledWith(warning);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
});

it('should allow the method to succeed', async () => {
const [provider, mockPort] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
mockPort.reply('metamask-provider', {
id,
jsonrpc: '2.0',
result: 'success!',
});
},
},
],
});

const response = await provider.request({ method });

expect(response).toBe('success!');
});

it('should allow the method to fail', async () => {
const [provider, mockPort] = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
mockPort.reply('metamask-provider', {
id,
jsonrpc: '2.0',
error: { code: 0, message: 'failure!' },
});
},
},
],
});

await expect(() =>
provider.request({ method }),
).rejects.toMatchObject({
code: 0,
message: 'failure!',
});
});
});
}
});
});
4 changes: 4 additions & 0 deletions src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const messages = {
networkChanged: `MetaMask: The event 'networkChanged' is deprecated and may be removed in the future. Use 'chainChanged' instead.\nFor more information, see: https://eips.ethereum.org/EIPS/eip-1193#chainchanged`,
notification: `MetaMask: The event 'notification' is deprecated and may be removed in the future. Use 'message' instead.\nFor more information, see: https://eips.ethereum.org/EIPS/eip-1193#message`,
},
rpc: {
ethDecryptDeprecation: `MetaMask: The RPC method 'eth_decrypt' is deprecated and may be removed in the future.\nFor more information, see: https://medium.com/metamask/metamask-api-method-deprecation-2b0564a84686`,
ethGetEncryptionPublicKeyDeprecation: `MetaMask: The RPC method 'eth_getEncryptionPublicKey' is deprecated and may be removed in the future.\nFor more information, see: https://medium.com/metamask/metamask-api-method-deprecation-2b0564a84686`,
},
// misc
experimentalMethods: `MetaMask: 'ethereum._metamask' exposes non-standard, experimental methods. They may be removed or changed without warning.`,
},
Expand Down
Loading

0 comments on commit 19bc09f

Please sign in to comment.