Skip to content

Commit

Permalink
Add SnapBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
gantunesr committed Dec 13, 2022
1 parent 440d6df commit 0f8d29d
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 1 deletion.
191 changes: 191 additions & 0 deletions app/core/SnapExecutionService/SnapBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* eslint-disable import/no-commonjs */
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable @typescript-eslint/no-var-requires */
// eslint-disable-next-line import/no-nodejs-modules
import { Duplex } from 'stream';
import {
createSwappableProxy,
createEventEmitterProxy,
} from 'swappable-obj-proxy';
import { JsonRpcEngine } from 'json-rpc-engine';
import { createEngineStream } from 'json-rpc-middleware-stream';
import { NetworksChainId } from '@metamask/controllers';

import Engine from '../Engine';
import { setupMultiplex } from '../../util/streams';
import {
createOriginMiddleware,
createLoggerMiddleware,
} from '../../util/middlewares';
import Logger from '../../util/Logger';
import { getAllNetworks } from '../../util/networks';

const ObjectMultiplex = require('obj-multiplex');
const createFilterMiddleware = require('eth-json-rpc-filters');
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager');
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware');
const pump = require('pump');

interface ISnapBridgeProps {
snapId: string;
stream: Duplex;
getRPCMethodMiddleware: (args: any) => any;
}

export default class SnapBridge {
snapId: string;
stream: Duplex;
getRPCMethodMiddleware: (args: any) => any;
provider: any;
blockTracker: any;

#mux: typeof ObjectMultiplex;
#providerProxy: any;
#blockTrackerProxy: any;

constructor({ snapId, stream, getRPCMethodMiddleware }: ISnapBridgeProps) {
this.snapId = snapId;
this.stream = stream;
this.getRPCMethodMiddleware = getRPCMethodMiddleware;

const { NetworkController, PreferencesController } = Engine.context as any;

const provider = NetworkController.provider;
const blockTracker = provider._blockTracker;

this.#providerProxy = null;
this.#blockTrackerProxy = null;

this.#setProvider(provider);
this.#setBlockTracker(blockTracker);

this.#mux = setupMultiplex(this.stream);
}

#setProvider = (provider: any): void => {
if (this.#providerProxy) {
this.#providerProxy.setTarget(provider);
} else {
this.#providerProxy = createSwappableProxy(provider);
}
this.provider = provider;
};

#setBlockTracker = (blockTracker: any): void => {
if (this.#blockTrackerProxy) {
this.#blockTrackerProxy.setTarget(blockTracker);
} else {
this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, {
eventFilter: 'skipInternal',
});
}
this.blockTracker = blockTracker;
};

getProviderState() {
const memState = this.getState();
return {
isUnlocked: this.isUnlocked(),
...this.getProviderNetworkState(memState),
};
}

setupProviderConnection = () => {
console.log('method setupProviderConnection');
const outStream = this.#mux.createStream('metamask-provider');
const engine = this.setupProviderEngine();
const providerStream = createEngineStream({ engine });
pump(outStream, providerStream, outStream, (err: any) => {
// handle any middleware cleanup
engine._middleware.forEach((mid: any) => {
if (mid.destroy && typeof mid.destroy === 'function') {
mid.destroy();
}
});
if (err) Logger.log('Error with provider stream conn', err);
});
};

setupProviderEngine = () => {
const engine = new JsonRpcEngine();

// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({
provider: this.#providerProxy,
blockTracker: this.#blockTrackerProxy,
});

// create subscription polyfill middleware
const subscriptionManager = createSubscriptionManager({
provider: this.#providerProxy,
blockTracker: this.#blockTrackerProxy,
});

subscriptionManager.events.on('notification', (message: any) =>
engine.emit('notification', message),
);

engine.push(createOriginMiddleware({ origin: this.snapId }));
engine.push(createLoggerMiddleware({ origin: this.snapId }));

// Filter and subscription polyfills
engine.push(filterMiddleware);
engine.push(subscriptionManager.middleware);

// User-Facing RPC methods
engine.push(
this.getRPCMethodMiddleware({
hostname: this.snapId,
getProviderState: this.getProviderState.bind(this),
}),
);

// Forward to metamask primary provider
engine.push(providerAsMiddleware(this.#providerProxy));
return engine;
};

getNetworkState = ({ network }: { network: string }) => {
const { NetworkController } = Engine.context as any;
const networkType = NetworkController.state.provider.type;
const networkProvider = NetworkController.state.provider;

const isInitialNetwork =
networkType && getAllNetworks().includes(networkType);
let chainId;

if (isInitialNetwork) {
chainId = NetworksChainId[networkType];
} else if (networkType === 'rpc') {
chainId = networkProvider.chainId;
}
if (chainId && !chainId.startsWith('0x')) {
// Convert to hex
chainId = `0x${parseInt(chainId, 10).toString(16)}`;
}

const result = {
networkVersion: network,
chainId,
};
return result;
};

isUnlocked = (): boolean => {
const { KeyringController } = Engine.context as any;
return KeyringController.isUnlocked();
};

getState = () => {
const { context, datamodel } = Engine;
const { KeyringController } = context as any;
const vault = KeyringController.state.vault;
const { network, selectedAddress } = datamodel.flatState as any;
return {
isInitialized: !!vault,
isUnlocked: true,
network,
selectedAddress,
};
};
}
3 changes: 2 additions & 1 deletion app/core/SnapExecutionService/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SnapExecutionService from './SnapExecutionService';
import SnapBridge from './SnapBridge';

// eslint-disable-next-line import/prefer-default-export
export { SnapExecutionService };
export { SnapExecutionService, SnapBridge };

0 comments on commit 0f8d29d

Please sign in to comment.