Skip to content

Commit df85bdf

Browse files
authored
refactor(StreamProvider): remove unnecessary stream multiplexing (#400)
* refactor(StreamProvider): remove unnecessary stream multiplexing * fix: StreamProvider unit tests * fix: inpage provider unit tests * fix: adjust coverage (will be reverted) * fix: in page providerconstructor type * fix: add changelog * fix: changelog linting * fix: changelog highlight in page provider * fix: remove useless undefined for jsonRpcStreamName
1 parent 5b1241d commit df85bdf

File tree

8 files changed

+89
-56
lines changed

8 files changed

+89
-56
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- **BREAKING**: `StreamProvider` no longer accepts a `jsonRpcStreamName` parameter ([#400](https://github.com/MetaMask/providers/pull/400))
13+
- Previously, this parameter was used internally to create an ObjectMultiplex stream and substream for JSON-RPC communication
14+
- Now, the consumer is responsible for creating and managing the stream multiplexing if needed
15+
- The provider will use the provided stream connection directly without any multiplexing
16+
- **BREAKING**: `MetaMaskInpageProvider` no longer accepts a `jsonRpcStreamName` parameter ([#400](https://github.com/MetaMask/providers/pull/400))
17+
- This change is inherited from StreamProvider, as MetaMaskInpageProvider extends StreamProvider
18+
- Stream multiplexing should be handled before provider instantiation
19+
- `initializeInpageProvider` now handles stream multiplexing internally ([#400](https://github.com/MetaMask/providers/pull/400))
20+
- Creates an ObjectMultiplex instance and substream using the provided `jsonRpcStreamName`
21+
- This maintains backwards compatibility for consumers using `initializeInpageProvider`
22+
- `createExternalExtensionProvider` now handles stream multiplexing internally ([#400](https://github.com/MetaMask/providers/pull/400))
23+
- Creates an ObjectMultiplex instance and substream for JSON-RPC communication
24+
- This maintains backwards compatibility for consumers using `createExternalExtensionProvider`
25+
1026
## [18.3.1]
1127

1228
### Changed

jest.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ const baseConfig = {
4545
// An object that configures minimum threshold enforcement for coverage results
4646
coverageThreshold: {
4747
global: {
48-
branches: 67.6,
49-
functions: 69.91,
50-
lines: 69.51,
51-
statements: 69.52,
48+
branches: 66.79,
49+
functions: 68.69,
50+
lines: 68.35,
51+
statements: 68.38,
5252
},
5353
},
5454

src/MetaMaskInpageProvider.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import ObjectMultiplex from '@metamask/object-multiplex';
12
import type { JsonRpcRequest } from '@metamask/utils';
3+
import { pipeline } from 'readable-stream';
24

35
import messages from './messages';
46
import {
@@ -64,14 +66,14 @@ async function getInitializedProvider({
6466
const onWrite = jest.fn();
6567
const connectionStream = new MockConnectionStream((name, data) => {
6668
if (
67-
name === 'metamask-provider' &&
69+
name === MetaMaskInpageProviderStreamName &&
6870
data.method === 'metamask_getProviderState'
6971
) {
7072
// Wrap in `setTimeout` to ensure a reply is received by the provider
7173
// after the provider has processed the request, to ensure that the
7274
// provider recognizes the id.
7375
setTimeout(() =>
74-
connectionStream.reply('metamask-provider', {
76+
connectionStream.reply(MetaMaskInpageProviderStreamName, {
7577
id: onWrite.mock.calls[0][1].id,
7678
jsonrpc: '2.0',
7779
result: {
@@ -93,8 +95,13 @@ async function getInitializedProvider({
9395
}
9496
onWrite(name, data);
9597
});
96-
97-
const provider = new MetaMaskInpageProvider(connectionStream);
98+
const mux = new ObjectMultiplex();
99+
pipeline(connectionStream, mux, connectionStream, (error: Error | null) => {
100+
console.error(error);
101+
});
102+
const provider = new MetaMaskInpageProvider(
103+
mux.createStream(MetaMaskInpageProviderStreamName),
104+
);
98105
await new Promise<void>((resolve: () => void) => {
99106
provider.on('_initialized', resolve);
100107
});

src/MetaMaskInpageProvider.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ export type MetaMaskInpageProviderOptions = {
2929
* Whether the provider should send page metadata.
3030
*/
3131
shouldSendMetadata?: boolean;
32-
33-
jsonRpcStreamName?: string | undefined;
34-
} & Partial<Omit<StreamProviderOptions, 'rpcMiddleware' | 'jsonRpcStreamName'>>;
32+
} & Partial<Omit<StreamProviderOptions, 'rpcMiddleware'>>;
3533

3634
type SentWarningsState = {
3735
// methods
@@ -86,8 +84,6 @@ export class MetaMaskInpageProvider extends AbstractStreamProvider {
8684
*
8785
* @param connectionStream - A Node.js duplex stream.
8886
* @param options - An options bag.
89-
* @param options.jsonRpcStreamName - The name of the internal JSON-RPC stream.
90-
* Default: `metamask-provider`.
9187
* @param options.logger - The logging API to use. Default: `console`.
9288
* @param options.maxEventListeners - The maximum number of event
9389
* listeners. Default: 100.
@@ -97,14 +93,12 @@ export class MetaMaskInpageProvider extends AbstractStreamProvider {
9793
constructor(
9894
connectionStream: Duplex,
9995
{
100-
jsonRpcStreamName = MetaMaskInpageProviderStreamName,
10196
logger = console,
10297
maxEventListeners = 100,
10398
shouldSendMetadata,
10499
}: MetaMaskInpageProviderOptions = {},
105100
) {
106101
super(connectionStream, {
107-
jsonRpcStreamName,
108102
logger,
109103
maxEventListeners,
110104
rpcMiddleware: getDefaultExternalMiddleware(logger),

src/StreamProvider.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';
2+
import ObjectMultiplex from '@metamask/object-multiplex';
23
import type { Json, JsonRpcParams } from '@metamask/utils';
4+
import { pipeline } from 'readable-stream';
35

46
import messages from './messages';
57
import { StreamProvider } from './StreamProvider';
@@ -20,7 +22,6 @@ function getStreamProvider(
2022
) {
2123
const mockStream = new MockConnectionStream();
2224
const streamProvider = new StreamProvider(mockStream, {
23-
jsonRpcStreamName: mockStreamName,
2425
rpcMiddleware,
2526
});
2627

@@ -38,9 +39,7 @@ describe('StreamProvider', () => {
3839
const networkVersion = '1';
3940
const isUnlocked = true;
4041

41-
const streamProvider = new StreamProvider(new MockConnectionStream(), {
42-
jsonRpcStreamName: mockStreamName,
43-
});
42+
const streamProvider = new StreamProvider(new MockConnectionStream());
4443

4544
const requestMock = jest
4645
.spyOn(streamProvider, 'request')
@@ -370,10 +369,13 @@ describe('StreamProvider', () => {
370369
describe('events', () => {
371370
it('calls chainChanged when the chainId changes', async () => {
372371
const mockStream = new MockConnectionStream();
373-
const streamProvider = new StreamProvider(mockStream, {
374-
jsonRpcStreamName: mockStreamName,
372+
const mux = new ObjectMultiplex();
373+
pipeline(mockStream, mux, mockStream, (error: Error | null) => {
374+
console.error(error);
375375
});
376-
376+
const streamProvider = new StreamProvider(
377+
mux.createStream(mockStreamName),
378+
);
377379
const requestMock = jest
378380
.spyOn(streamProvider, 'request')
379381
.mockImplementationOnce(async () => {
@@ -404,9 +406,13 @@ describe('StreamProvider', () => {
404406

405407
it('handles chain changes with intermittent disconnection', async () => {
406408
const mockStream = new MockConnectionStream();
407-
const streamProvider = new StreamProvider(mockStream, {
408-
jsonRpcStreamName: mockStreamName,
409+
const mux = new ObjectMultiplex();
410+
pipeline(mockStream, mux, mockStream, (error: Error | null) => {
411+
console.error(error);
409412
});
413+
const streamProvider = new StreamProvider(
414+
mux.createStream(mockStreamName),
415+
);
410416

411417
const requestMock = jest
412418
.spyOn(streamProvider, 'request')

src/StreamProvider.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';
22
import { createStreamMiddleware } from '@metamask/json-rpc-middleware-stream';
3-
import ObjectMultiplex from '@metamask/object-multiplex';
43
import type SafeEventEmitter from '@metamask/safe-event-emitter';
54
import type { Json, JsonRpcParams } from '@metamask/utils';
65
import { duplex as isDuplex } from 'is-stream';
@@ -16,12 +15,7 @@ import {
1615
isValidNetworkVersion,
1716
} from './utils';
1817

19-
export type StreamProviderOptions = {
20-
/**
21-
* The name of the stream used to connect to the wallet.
22-
*/
23-
jsonRpcStreamName: string;
24-
} & BaseProviderOptions;
18+
export type StreamProviderOptions = BaseProviderOptions;
2519

2620
export type JsonRpcConnection = {
2721
events: SafeEventEmitter;
@@ -43,7 +37,6 @@ export abstract class AbstractStreamProvider extends BaseProvider {
4337
*
4438
* @param connectionStream - A Node.js duplex stream.
4539
* @param options - An options bag.
46-
* @param options.jsonRpcStreamName - The name of the internal JSON-RPC stream.
4740
* @param options.logger - The logging API to use. Default: `console`.
4841
* @param options.maxEventListeners - The maximum number of event
4942
* listeners. Default: 100.
@@ -52,11 +45,10 @@ export abstract class AbstractStreamProvider extends BaseProvider {
5245
constructor(
5346
connectionStream: Duplex,
5447
{
55-
jsonRpcStreamName,
5648
logger = console,
5749
maxEventListeners = 100,
5850
rpcMiddleware = [],
59-
}: StreamProviderOptions,
51+
}: StreamProviderOptions = {},
6052
) {
6153
super({ logger, maxEventListeners, rpcMiddleware });
6254

@@ -67,15 +59,6 @@ export abstract class AbstractStreamProvider extends BaseProvider {
6759
// Bind functions to prevent consumers from making unbound calls
6860
this._handleStreamDisconnect = this._handleStreamDisconnect.bind(this);
6961

70-
// Set up connectionStream multiplexing
71-
const mux = new ObjectMultiplex();
72-
pipeline(
73-
connectionStream,
74-
mux as unknown as Duplex,
75-
connectionStream,
76-
this._handleStreamDisconnect.bind(this, 'MetaMask'),
77-
);
78-
7962
// Set up RPC connection
8063
// Typecast: The type of `Duplex` is incompatible with the type of
8164
// `JsonRpcConnection`.
@@ -84,9 +67,9 @@ export abstract class AbstractStreamProvider extends BaseProvider {
8467
}) as unknown as JsonRpcConnection;
8568

8669
pipeline(
70+
connectionStream,
8771
this._jsonRpcConnection.stream,
88-
mux.createStream(jsonRpcStreamName) as unknown as Duplex,
89-
this._jsonRpcConnection.stream,
72+
connectionStream,
9073
this._handleStreamDisconnect.bind(this, 'MetaMask RpcProvider'),
9174
);
9275

src/extension-provider/createExternalExtensionProvider.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import ObjectMultiplex from '@metamask/object-multiplex';
12
import { detect } from 'detect-browser';
23
import { PortDuplexStream as PortStream } from 'extension-port-stream';
3-
import type { Duplex } from 'readable-stream';
4+
import { pipeline } from 'readable-stream';
45
import type { Runtime } from 'webextension-polyfill';
56

67
import config from './external-extension-config.json';
@@ -28,8 +29,16 @@ export function createExternalExtensionProvider(
2829
const metamaskPort = chrome.runtime.connect(extensionId) as Runtime.Port;
2930

3031
const pluginStream = new PortStream(metamaskPort);
31-
provider = new StreamProvider(pluginStream as unknown as Duplex, {
32-
jsonRpcStreamName: MetaMaskInpageProviderStreamName,
32+
const streamName = MetaMaskInpageProviderStreamName;
33+
const mux = new ObjectMultiplex();
34+
pipeline(pluginStream, mux, pluginStream, (error: Error | null) => {
35+
let warningMsg = `Lost connection to "${streamName}".`;
36+
if (error?.stack) {
37+
warningMsg += `\n${error.stack}`;
38+
}
39+
console.warn(warningMsg);
40+
});
41+
provider = new StreamProvider(mux.createStream(streamName), {
3342
logger: console,
3443
rpcMiddleware: getDefaultExternalMiddleware(console),
3544
});

src/initializeInpageProvider.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import type { Duplex } from 'readable-stream';
1+
import ObjectMultiplex from '@metamask/object-multiplex';
2+
import { type Duplex, pipeline } from 'readable-stream';
23

34
import type { CAIP294WalletData } from './CAIP294';
45
import { announceWallet } from './CAIP294';
56
import { announceProvider as announceEip6963Provider } from './EIP6963';
67
import { getBuildType } from './extension-provider/createExternalExtensionProvider';
78
import type { MetaMaskInpageProviderOptions } from './MetaMaskInpageProvider';
8-
import { MetaMaskInpageProvider } from './MetaMaskInpageProvider';
9+
import {
10+
MetaMaskInpageProvider,
11+
MetaMaskInpageProviderStreamName,
12+
} from './MetaMaskInpageProvider';
913
import { shimWeb3 } from './shimWeb3';
1014
import type { BaseProviderInfo } from './types';
1115

@@ -29,6 +33,10 @@ type InitializeProviderOptions = {
2933
* Whether the window.web3 shim should be set.
3034
*/
3135
shouldShimWeb3?: boolean;
36+
/**
37+
* The name of the stream used to connect to the wallet.
38+
*/
39+
jsonRpcStreamName?: string;
3240
} & MetaMaskInpageProviderOptions;
3341

3442
/**
@@ -47,20 +55,30 @@ type InitializeProviderOptions = {
4755
*/
4856
export function initializeProvider({
4957
connectionStream,
50-
jsonRpcStreamName,
58+
jsonRpcStreamName = MetaMaskInpageProviderStreamName,
5159
logger = console,
5260
maxEventListeners = 100,
5361
providerInfo,
5462
shouldSendMetadata = true,
5563
shouldSetOnWindow = true,
5664
shouldShimWeb3 = false,
5765
}: InitializeProviderOptions): MetaMaskInpageProvider {
58-
const provider = new MetaMaskInpageProvider(connectionStream, {
59-
jsonRpcStreamName,
60-
logger,
61-
maxEventListeners,
62-
shouldSendMetadata,
66+
const mux = new ObjectMultiplex();
67+
pipeline(connectionStream, mux, connectionStream, (error: Error | null) => {
68+
let warningMsg = `Lost connection to "${jsonRpcStreamName}".`;
69+
if (error?.stack) {
70+
warningMsg += `\n${error.stack}`;
71+
}
72+
console.warn(warningMsg);
6373
});
74+
const provider = new MetaMaskInpageProvider(
75+
mux.createStream(jsonRpcStreamName),
76+
{
77+
logger,
78+
maxEventListeners,
79+
shouldSendMetadata,
80+
},
81+
);
6482

6583
const proxiedProvider = new Proxy(provider, {
6684
// some common libraries, e.g. [email protected], mess with our API

0 commit comments

Comments
 (0)