Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deprecation warning for encryption methods #218

Merged
merged 1 commit into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
122 changes: 122 additions & 0 deletions src/MetaMaskInpageProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,128 @@ 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
210 changes: 210 additions & 0 deletions src/extension-provider/createExternalExtensionProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,104 @@
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 { createExternalExtensionProvider } from './createExternalExtensionProvider';

/**
* A fully initialized extension provider, and additional mocks to help
* test the provider.
*/
interface InitializedExtensionProviderDetails {
/** The initialized provider, created using a mocked Port instance. */
provider: StreamProvider;
/** The mock Port instance used to create the provider. */
port: MockPort;
/** A mock function that can be used to inspect what gets written to the
* mock connection Stream.
*/
onWrite: ReturnType<typeof jest.fn>;
}

/**
* 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;
}[];
} = {}): Promise<InitializedExtensionProviderDetails> {
const onWrite = jest.fn();
const port = new MockPort((name, data) => {
if (
name === 'metamask-provider' &&
data.method === 'metamask_getProviderState'
) {
// 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(() =>
port.reply('metamask-provider', {
id: onWrite.mock.calls[0][1].id,
jsonrpc: '2.0',
result: {
accounts,
chainId,
isUnlocked,
networkVersion,
},
}),
);
}
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` mock setup by `jest-chrome` in `jest.setup.js`
(global.chrome.runtime.connect as any).mockImplementation(() => {
return port;
});

const provider = createExternalExtensionProvider();
await new Promise<void>((resolve: () => void) => {
provider.on('_initialized', resolve);
});

return { provider, port, onWrite };
}

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 +125,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, port } = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
port.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, port } = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
port.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, port } = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
port.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, port } = await getInitializedProvider({
onMethodCalled: [
{
substream: 'metamask-provider',
method,
callback: ({ id }) => {
port.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