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

Use new MetaMask "ethereum" provider #366

Merged
merged 4 commits into from
Oct 29, 2018
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
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