Skip to content

Commit

Permalink
Use new MetaMask "ethereum" provider (#366)
Browse files Browse the repository at this point in the history
  • Loading branch information
dkent600 authored Oct 29, 2018
1 parent 8a41450 commit 5b3023b
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 69 deletions.
94 changes: 88 additions & 6 deletions lib/accountService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { promisify } from "es6-promisify";
import { Address } from "./commonTypes";
import { LoggingService } from "./loggingService";
import { IEventSubscription, PubSubEventService } from "./pubSubEventService";
import { Utils } from "./utils";
import { Utils, Web3 } from "./utils";
import { UtilsInternal } from "./utilsInternal";

/**
* Watch for changes in the default account.
Expand All @@ -11,6 +13,7 @@ import { Utils } from "./utils";
export class AccountService {

public static AccountChangedEventTopic: string = "AccountService.account.changed";
public static NetworkChangedEventTopic: string = "AccountService.network.changed";

/**
* Initializes the system that watches for default account changes.
Expand All @@ -20,9 +23,6 @@ export class AccountService {
*
* Then you may request to be notified whenever the current account changes by calling
* [AccountService.subscribeToAccountChanges](/api/classes/AccountService#subscribeToAccountChanges)
*
*
* @param web3
*/
public static async initiateAccountWatch(): Promise<void> {

Expand Down Expand Up @@ -63,6 +63,56 @@ export class AccountService {
}, 1000);
}

/**
* Initializes the system that watches for blockchain network id changes.
*
* `initiateNetworkWatch` is called automatically by Arc.js when you pass `true`
* for `watchForNetworkChanges` to `InitializeArcJs`. You may also call it manually yourself.
*
* Then you may request to be notified whenever the current account changes by calling
* [AccountService.subscribeToNetworkChanges](/api/classes/AccountService#subscribeToNetworkChanges)
*
* When the network is found to have changed you should call `InitializeArcJs` so Arc.js will set
* itself up with the new network and return to you a new `Web3` object.
*/
public static async initiateNetworkWatch(): Promise<void> {

if (AccountService.networkChangedTimerId) {
return;
}

LoggingService.info("Initiating account watch");

if (!AccountService.currentNetworkId) {
try {
AccountService.currentNetworkId = await AccountService.getNetworkId();
} catch {
AccountService.currentNetworkId = undefined;
}
}

AccountService.networkChangedTimerId = setInterval(async () => {

if (AccountService.networkChangedLock) {
return; // prevent reentrance
}

AccountService.networkChangedLock = true;

let currentNetworkId = AccountService.currentNetworkId;
try {
currentNetworkId = await AccountService.getNetworkId();
} catch {
currentNetworkId = undefined;
}
if (currentNetworkId !== AccountService.currentNetworkId) {
AccountService.currentNetworkId = currentNetworkId;
LoggingService.info(`Network watch: network changed: ${currentNetworkId}`);
PubSubEventService.publish(AccountService.NetworkChangedEventTopic, currentNetworkId);
}
AccountService.networkChangedLock = false;
}, 1000);
}
/**
* Turn off the system that watches for default account changes.
*/
Expand All @@ -73,11 +123,20 @@ export class AccountService {
}
}

/**
* Turn off the system that watches for default account changes.
*/
public static endNetworkWatch(): void {
if (AccountService.networkChangedTimerId) {
clearInterval(AccountService.networkChangedTimerId);
AccountService.networkChangedTimerId = undefined;
}
}
/**
* Subscribe to be notified whenever the current account changes, like this:
*
* ```javascript
* AccountService.subscribeToAccountChanges((account: Address) => { ... });
* ```typescript
* AccountService.subscribeToAccountChanges((account: Address): void => { ... });
* ```
* @param callback
* @returns A subscription to the event. Unsubscribe by calling `[theSubscription].unsubscribe()`.
Expand All @@ -87,7 +146,30 @@ export class AccountService {
(topic: string, address: Address): any => callback(address));
}

/**
* Subscribe to be notified whenever the current network changes, like this:
*
* ```typescript
* AccountService.subscribeToAccountChanges((networkId: number): void => { ... });
* ```
* @param callback
* @returns A subscription to the event. Unsubscribe by calling `[theSubscription].unsubscribe()`.
*/
public static subscribeToNetworkChanges(callback: (networkId: number) => void): IEventSubscription {
return PubSubEventService.subscribe(AccountService.NetworkChangedEventTopic,
(topic: string, networkId: number): any => callback(networkId));
}

private static currentAccount: Address | undefined;
private static currentNetworkId: number | undefined;
private static accountChangedLock: boolean = false;
private static accountChangedTimerId: any;
private static networkChangedLock: boolean = false;
private static networkChangedTimerId: any;

private static async getNetworkId(): Promise<number | undefined> {
const web3 = UtilsInternal.getWeb3Sync();
return web3 ?
Number.parseInt(await promisify(web3.version.getNetwork)() as string, 10) as number | undefined : undefined;
}
}
4 changes: 4 additions & 0 deletions lib/contractWrapperFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export class ContractWrapperFactory<TWrapper extends IContractWrapper>
ContractWrapperFactory.configService = configService;
}

public static clearContractCache(): void {
ContractWrapperFactory.contractCache.clear();
}

/**
* this is a Map keyed by contract name of a Map keyed by address to an `IContractWrapper`
*/
Expand Down
39 changes: 35 additions & 4 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,31 +64,58 @@ import { WrapperService, WrapperServiceInitializeOptions } from "./wrapperServic
* Options for [InitializeArcJs](/api/README/#initializearcjs)
*/
export interface InitializeArcOptions extends WrapperServiceInitializeOptions {
/**
* If `true` and `window.ethereum` is present, instantiate Web3 using it as the provider.
* Ignored if `useWeb3` is given.
* Default is true.
*/
useMetamaskEthereumWeb3Provider?: boolean;
/**
* Name of the network for which to use the defaults found in Arc.js/truffle.js.
* Overwrites config settings network, providerUrl and providerPort.
* See [Network settings](Home#networksettings) for more information.
*/
useNetworkDefaultsFor?: string;
/**
* Optionally use the given Web3 instance, already initialized by a provider.
* `useMetamaskEthereumWeb3Provider` is ignored if this is given.
* Has the side-effect of setting `global.web3` to the given value.
*/
useWeb3?: Web3;
/**
* Set to true to watch for changes in the current user account.
* Default is false. See [AccountService.initiateAccountWatch](/api/classes/AccountService#initiateAccountWatch).
*/
watchForAccountChanges?: boolean;
/**
* Set to true to watch for changes in the current blockchain network.
* Default is false. See [AccountService.initiateNetworkWatch](/api/classes/AccountService#initiateNetworkWatch).
*/
watchForNetworkChanges?: boolean;
}
/**
* You must call `InitializeArcJs` before doing anything else with Arc.js.
* Call it again whenever the current chain changes.
* @returns Promise of the `Web3` object for the current chain.
*/
export async function InitializeArcJs(options?: InitializeArcOptions): Promise<Web3> {
export async function InitializeArcJs(options: InitializeArcOptions = {}): Promise<Web3> {
LoggingService.info("Initializing Arc.js");
try {

if (options.useWeb3) {
// Utils.getWeb3 will pick this up
(global as any).web3 = options.useWeb3;
} else if (typeof (options.useMetamaskEthereumWeb3Provider) === "undefined") {
options.useMetamaskEthereumWeb3Provider = true;
}

ConfigService.set("useMetamaskEthereumWeb3Provider", options.useMetamaskEthereumWeb3Provider);

/**
* simulate dependency injection, avoid circular dependencies
*/
ContractWrapperFactory.setConfigService(ConfigService);
ContractWrapperFactory.clearContractCache();
/**
* Initialize LoggingService here to avoid circular dependency involving ConfigService and PubSubService
*/
Expand All @@ -100,7 +127,7 @@ export async function InitializeArcJs(options?: InitializeArcOptions): Promise<W
LoggingService.logLevel = parseInt(value as any, 10) as LogLevel;
});

if (options && options.useNetworkDefaultsFor) {
if (options.useNetworkDefaultsFor) {
const networkDefaults = ConfigService.get("networkDefaults")[options.useNetworkDefaultsFor];
if (!networkDefaults) {
throw new Error(`truffle network defaults not found: ${options.useNetworkDefaultsFor}`);
Expand All @@ -110,13 +137,17 @@ export async function InitializeArcJs(options?: InitializeArcOptions): Promise<W
ConfigService.set("providerUrl", `${networkDefaults.host}`);
}

const web3 = await Utils.getWeb3();
const web3 = await Utils.getWeb3(true);
await WrapperService.initialize(options);

if (options && options.watchForAccountChanges) {
if (options.watchForAccountChanges) {
await AccountService.initiateAccountWatch();
}

if (options.watchForNetworkChanges) {
await AccountService.initiateNetworkWatch();
}

return web3;
} catch (ex) {
/* tslint:disable-next-line:no-bitwise */
Expand Down
Loading

0 comments on commit 5b3023b

Please sign in to comment.