Skip to content

Commit

Permalink
Merge branch 'develop' into add-epoch-history
Browse files Browse the repository at this point in the history
  • Loading branch information
vsubhuman committed Aug 8, 2022
2 parents 9da5868 + e1f0597 commit ea587a1
Show file tree
Hide file tree
Showing 26 changed files with 340 additions and 63 deletions.
4 changes: 4 additions & 0 deletions packages/yoroi-extension/app/App.js
Expand Up @@ -36,6 +36,7 @@ import { ThemeProvider } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { globalStyles } from './styles/globalStyles';
import Support from './components/widgets/Support';
import { trackNavigation } from './api/analytics';

// https://github.com/yahoo/react-intl/wiki#loading-locale-data
addLocaleData([
Expand Down Expand Up @@ -80,6 +81,9 @@ class App extends Component<Props, State> {
this.mergedMessages = _mergedMessages;
});
});
this.props.history.listen(({ pathname }) => {
trackNavigation(pathname);
});
}

state: State = {
Expand Down
171 changes: 171 additions & 0 deletions packages/yoroi-extension/app/api/analytics/index.js
@@ -0,0 +1,171 @@
// @flow
import cryptoRandomString from 'crypto-random-string';
import querystring from 'querystring';

import LocalStorageApi, {
loadAnalyticsInstanceId,
saveAnalyticsInstanceId,
} from '../localStorage';
import { environment } from '../../environment';
import { TRACKED_ROUTES } from '../../routes-config';
import type { StoresMap } from '../../stores';
import { isTestnet as isTestnetFunc } from '../ada/lib/storage/database/prepackaged/networks';

const MATOMO_URL = 'https://analytics.emurgo-rnd.com/matomo.php';
const SITE_ID = '4';
let INSTANCE_ID;
let stores;

export async function trackStartup(stores_: StoresMap): Promise<void> {
stores = stores_;

let event;
if (await (new LocalStorageApi()).getUserLocale() != null) {
INSTANCE_ID = await loadAnalyticsInstanceId();
if (INSTANCE_ID) {
emitEvent(INSTANCE_ID, 'launch');
return;
}
event = 'pre-existing-instance';
} else {
event = 'new-instance';
}
INSTANCE_ID = generateAnalyticsInstanceId();
await saveAnalyticsInstanceId(INSTANCE_ID);
emitEvent(INSTANCE_ID, event);
}

type NewWalletType = 'hardware' | 'created' | 'restored';

export function trackWalletCreation(newWalletType: NewWalletType): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'new-wallet/' + newWalletType);
}

export function trackNavigation(path: string): void {
if (path.match(TRACKED_ROUTES)) {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'navigate' + path);
}
}

export function trackSend(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'new-transaction/send');
}

export function trackDelegation(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'delegation');
}

export function trackWithdrawal(shouldDeregister: boolean): void {
if (INSTANCE_ID == null) {
return;
}
if (shouldDeregister) {
emitEvent(INSTANCE_ID, 'deregister');
} else {
emitEvent(INSTANCE_ID, 'withdrawal');
}
}

export function trackCatalystRegistration(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'new-transaction/catalyst');
}

export function trackSetLocale(locale: string): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'set-locale/' + locale);
}

export function trackSetUnitOfAccount(unitOfAccount: string): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'unit-of-account/' + unitOfAccount);
}

export function trackUpdateTheme(theme: string): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'update-theme/' + theme);
}

export function trackUriPrompt(choice: 'skip' | 'allow'): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'uri-prompt/' + choice);
}

export function trackBuySellDialog(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'buy-sell-ada');
}

export function trackExportWallet(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'export-wallet');
}

export function trackRemoveWallet(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'remove-wallet');
}

export function trackResyncWallet(): void {
if (INSTANCE_ID == null) {
return;
}
emitEvent(INSTANCE_ID, 'resync-wallet');
}

function generateAnalyticsInstanceId(): string {
// Matomo requires 16 character hex string
return cryptoRandomString({ length: 16 });
}

function emitEvent(instanceId: string, event: string): void {
if (environment.isDev() || environment.isTest()) {
return;
}

const isTestnet = stores.profile.selectedNetwork != null ?
isTestnetFunc(stores.profile.selectedNetwork) :
false;

// https://developer.matomo.org/api-reference/tracking-api
const params = {
idsite: SITE_ID,
rec: '1',
action_name: (isTestnet ? 'testnet/' : '') + event,
url: `yoroi.extension/${isTestnet ? 'testnet/' : ''}${event}`,
_id: INSTANCE_ID,
rand: `${Date.now()}-${Math.random()}`,
apiv: '1'
};
const url = `${MATOMO_URL}?${querystring.stringify(params)}`;

fetch(url);
}
10 changes: 10 additions & 0 deletions packages/yoroi-extension/app/api/localStorage/index.js
Expand Up @@ -32,6 +32,7 @@ const storageKeys = {
TOGGLE_SIDEBAR: networkForLocalStorage + '-TOGGLE-SIDEBAR',
WALLETS_NAVIGATION: networkForLocalStorage + '-WALLETS-NAVIGATION',
SUBMITTED_TRANSACTIONS: 'submittedTransactions',
ANALYTICS_INSTANCE_ID: networkForLocalStorage + '-ANALYTICS',
// ========== CONNECTOR ========== //
ERGO_CONNECTOR_WHITELIST: 'connector_whitelist',
};
Expand Down Expand Up @@ -360,3 +361,12 @@ export function loadSubmittedTransactions(): any {
}
return JSON.parse(dataStr);
}

export async function loadAnalyticsInstanceId(): Promise<?string> {
return getLocalItem(storageKeys.ANALYTICS_INSTANCE_ID);
}

export async function saveAnalyticsInstanceId(id: string): Promise<void> {
await setLocalItem(storageKeys.ANALYTICS_INSTANCE_ID, id);
}

Expand Up @@ -15,6 +15,7 @@ import { ReactComponent as VerifyIcon } from '../../assets/images/verify-icon.i
import VerticalFlexContainer from '../layout/VerticalFlexContainer'
import LoadingSpinner from '../widgets/LoadingSpinner'
import globalMessages from '../../i18n/global-messages';
import { trackBuySellDialog } from '../../api/analytics';

const messages = defineMessages({
dialogTitle: {
Expand Down Expand Up @@ -77,6 +78,7 @@ export default class BuySellDialog extends Component<Props, State> {
}
]
this.setState({ walletList: wallets })
trackBuySellDialog();
}

createRows: ($npm$ReactIntl$IntlFormat, Array<WalletInfo>) => Node = (intl, wallets) => (
Expand Down
Expand Up @@ -20,6 +20,7 @@ import {
} from '../../../../api/ada/lib/storage/models/ConceptualWallet';
import type { SendUsingLedgerParams } from '../../../../actions/ada/ledger-send-actions';
import type { SendUsingTrezorParams } from '../../../../actions/ada/trezor-send-actions';
import { trackSend } from '../../../../api/analytics';

export type GeneratedData = typeof WalletSendPreviewStepContainer.prototype.generated;

Expand Down Expand Up @@ -90,6 +91,7 @@ export default class WalletSendPreviewStepContainer extends Component<Props> {
onSuccess: openTransactionSuccessDialog,
});
}
trackSend()
}

render(): Node {
Expand Down
Expand Up @@ -21,6 +21,7 @@ import type { $npm$ReactIntl$IntlFormat } from 'react-intl';
import type { ServerStatusErrorType } from '../../types/serverStatusErrorType';
import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index';
import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks';
import { trackUriPrompt } from '../../api/analytics';

type GeneratedData = typeof UriPromptPage.prototype.generated;

Expand All @@ -39,10 +40,12 @@ export default class UriPromptPage extends Component<InjectedOrGenerated<Generat
runInAction(() => {
this.isAccepted = true;
});
trackUriPrompt('allow');
};

onSkip: void => void = () => {
this.generated.actions.profile.acceptUriScheme.trigger()
trackUriPrompt('skip');
};

onBack: void => void = () => {
Expand Down
Expand Up @@ -23,6 +23,7 @@ import { SelectedExplorer } from '../../../domain/SelectedExplorer';
import type {
GetAllExplorersResponse,
} from '../../../api/ada/lib/storage/bridge/explorers';
import { trackUriPrompt } from '../../../api/analytics';

type GeneratedData = typeof BlockchainSettingsPage.prototype.generated;

Expand Down Expand Up @@ -50,7 +51,12 @@ export default class BlockchainSettingsPage extends Component<InjectedOrGenerate
)
? (
<UriSettingsBlock
registerUriScheme={() => registerProtocols()}
registerUriScheme={
() => {
registerProtocols();
trackUriPrompt('allow');
}
}
isFirefox={environment.userAgentInfo.isFirefox()}
/>
)
Expand Down
Expand Up @@ -18,6 +18,7 @@ import { ReactComponent as AdaCurrency } from '../../../assets/images/currencie
import { unitOfAccountDisabledValue } from '../../../types/unitOfAccountType';
import type { UnitOfAccountSettingType } from '../../../types/unitOfAccountType';
import type { $npm$ReactIntl$IntlFormat } from 'react-intl';
import { trackSetUnitOfAccount, trackSetLocale } from '../../../api/analytics';

const currencyLabels = defineMessages({
USD: {
Expand Down Expand Up @@ -84,6 +85,12 @@ export default class GeneralSettingsPage extends Component<InjectedOrGenerated<G
? unitOfAccountDisabledValue
: { enabled: true, currency: value };
await this.generated.actions.profile.updateUnitOfAccount.trigger(unitOfAccount);
trackSetUnitOfAccount(value);
};

onSelectLanguage: {| locale: string |} => PossiblyAsync<void> = ({ locale }) => {
this.generated.actions.profile.updateLocale.trigger({ locale });
trackSetLocale(locale);
};

render(): Node {
Expand Down Expand Up @@ -120,7 +127,7 @@ export default class GeneralSettingsPage extends Component<InjectedOrGenerated<G
return (
<>
<GeneralSettings
onSelectLanguage={this.generated.actions.profile.updateLocale.trigger}
onSelectLanguage={this.onSelectLanguage}
isSubmitting={isSubmittingLocale}
languages={profileStore.LANGUAGE_OPTIONS}
currentLocale={profileStore.currentLocale}
Expand Down
Expand Up @@ -17,6 +17,7 @@ import { withLayout } from '../../../styles/context/layout';
import type { LayoutComponentMap } from '../../../styles/context/layout';
import { getWalletType } from '../../../stores/toplevel/WalletSettingsStore';
import type { WalletsNavigation } from '../../../api/localStorage'
import { trackRemoveWallet } from '../../../api/analytics';

export type GeneratedData = typeof RemoveWalletDialogContainer.prototype.generated;

Expand Down Expand Up @@ -75,6 +76,7 @@ class RemoveWalletDialogContainer extends Component<AllProps> {
quickAccess: walletsNavigation.quickAccess.filter(walletId => walletId !== selectedWalletId)
}
await this.generated.actions.profile.updateSortedWalletList.trigger(newWalletsNavigation);
trackRemoveWallet();
}

this.props.publicDeriver &&
Expand All @@ -99,11 +101,14 @@ class RemoveWalletDialogContainer extends Component<AllProps> {
onCancel={this.generated.actions.dialogs.closeActiveDialog.trigger}
primaryButton={{
label: intl.formatMessage(globalMessages.remove),
onClick: () =>
this.props.publicDeriver &&
settingsActions.removeWallet.trigger({
publicDeriver: this.props.publicDeriver,
}),
onClick: () => {
if (this.props.publicDeriver != null) {
settingsActions.removeWallet.trigger({
publicDeriver: this.props.publicDeriver,
});
trackRemoveWallet();
}
}
}}
secondaryButton={{
onClick: this.generated.actions.dialogs.closeActiveDialog.trigger,
Expand Down
Expand Up @@ -13,6 +13,7 @@ import type { InjectedOrGenerated } from '../../../types/injectedPropsType';

import DangerousActionDialog from '../../../components/widgets/DangerousActionDialog';
import LocalizableError from '../../../i18n/LocalizableError';
import { trackResyncWallet } from '../../../api/analytics';

export type GeneratedData = typeof ResyncWalletDialogContainer.prototype.generated;

Expand Down Expand Up @@ -65,6 +66,7 @@ export default class ResyncWalletDialogContainer extends Component<Props> {
publicDeriver: this.props.publicDeriver,
});
this.generated.actions.dialogs.closeActiveDialog.trigger();
trackResyncWallet();
}
}}
onCancel={this.generated.actions.dialogs.closeActiveDialog.trigger}
Expand Down
Expand Up @@ -27,6 +27,7 @@ import type { SigningKeyCache } from '../../../stores/toplevel/WalletStore';
import LocalizableError from '../../../i18n/LocalizableError';
import type { RenameModelFunc } from '../../../api/common/index';
import type { IGetSigningKey } from '../../../api/ada/lib/storage/models/PublicDeriver/interfaces';
import { trackExportWallet } from '../../../api/analytics';

type GeneratedData = typeof WalletSettingsPage.prototype.generated;

Expand Down Expand Up @@ -108,9 +109,12 @@ export default class WalletSettingsPage extends Component<InjectedOrGenerated<Ge
})}
/>
<ExportWallet
openDialog={() => actions.dialogs.open.trigger({
dialog: ExportWalletDialogContainer,
})}
openDialog={() => {
actions.dialogs.open.trigger({
dialog: ExportWalletDialogContainer,
});
trackExportWallet();
}}
/>
<RemoveWallet
walletName={settingsCache.conceptualWalletName}
Expand Down

0 comments on commit ea587a1

Please sign in to comment.