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

Refactor/cleanup experiments #1674

Merged
merged 5 commits into from
Jan 28, 2025
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
3 changes: 3 additions & 0 deletions apps/browser-extension-wallet/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,6 @@ MIDNIGHT_EVENT_BANNER_REMINDER_TIME=129600000
# Shared Wallet
SHARED_WALLET_TX_VALIDITY_INTERVAL=24
MIN_NUMBER_OF_COSIGNERS=2

# POLLING PAUSE AFTER INACTIVITY
SESSION_TIMEOUT=300000
3 changes: 3 additions & 0 deletions apps/browser-extension-wallet/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ MIDNIGHT_EVENT_BANNER_REMINDER_TIME=129600000
# Shared Wallet
SHARED_WALLET_TX_VALIDITY_INTERVAL=24
MIN_NUMBER_OF_COSIGNERS=2

# POLLING PAUSE AFTER INACTIVITY
SESSION_TIMEOUT=300000
4 changes: 2 additions & 2 deletions apps/browser-extension-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"@cardano-sdk/tx-construction": "0.26.0",
"@cardano-sdk/util": "0.15.5",
"@cardano-sdk/util-rxjs": "0.9.4",
"@cardano-sdk/wallet": "0.51.4",
"@cardano-sdk/web-extension": "0.38.4",
"@cardano-sdk/wallet": "0.51.5",
"@cardano-sdk/web-extension": "0.38.5",
"@emurgo/cip14-js": "~3.0.1",
"@input-output-hk/lace-ui-toolkit": "1.21.0",
"@lace/cardano": "0.1.0",
Expand Down
9 changes: 8 additions & 1 deletion apps/browser-extension-wallet/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type Config = {
SAVED_PRICE_DURATION: number;
DEFAULT_SUBMIT_API: string;
GOV_TOOLS_URLS: Record<EnvironmentTypes, string>;
SESSION_TIMEOUT: Milliseconds;
};

// eslint-disable-next-line complexity
Expand Down Expand Up @@ -143,6 +144,12 @@ export const config = (): Config => {
Preprod: `${process.env.GOV_TOOLS_URL_PREPROD}`,
Preview: `${process.env.GOV_TOOLS_URL_PREVIEW}`,
Sanchonet: `${process.env.GOV_TOOLS_URL_SANCHONET}`
}
},
// eslint-disable-next-line new-cap
SESSION_TIMEOUT: Milliseconds(
!Number.isNaN(Number.parseInt(process.env.SESSION_TIMEOUT))
? Number.parseInt(process.env.SESSION_TIMEOUT)
: 1000 * 60 * 5
)
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cip30 as walletCip30 } from '@cardano-sdk/wallet';
import { ensureUiIsOpenAndLoaded } from './util';
import { userPromptService } from './services/dappService';
import { authenticator } from './authenticator';
import { wallet$ } from './wallet';
import { wallet$, dAppConnectorActivity$ } from './wallet';
import { runtime, Tabs, tabs } from 'webextension-polyfill';
import { exposeApi, RemoteApiPropertyType, cip30 } from '@cardano-sdk/web-extension';
import { DAPP_CHANNELS } from '../../../utils/constants';
Expand All @@ -14,6 +14,7 @@ import pDebounce from 'p-debounce';
import { dappInfo$ } from './requestAccess';
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
import { getBackgroundStorage } from './storage';
import { notifyOnHaveAccessCall } from './session';

const DEBOUNCE_THROTTLE = 500;

Expand Down Expand Up @@ -108,7 +109,12 @@ const walletApi = walletCip30.createWalletApi(

cip30.initializeBackgroundScript(
{ walletName: process.env.WALLET_NAME },
{ authenticator, logger: console, runtime, walletApi }
{
authenticator: notifyOnHaveAccessCall(authenticator, dAppConnectorActivity$.next.bind(dAppConnectorActivity$)),
logger: console,
runtime,
walletApi
}
);

exposeApi<DappDataService>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const backgroundServiceProperties: RemoteApiProperties<BackgroundService>
unhandledError$: RemoteApiPropertyType.HotObservable
};

const { BLOCKFROST_CONFIGS, BLOCKFROST_RATE_LIMIT_CONFIG } = config();
const { BLOCKFROST_CONFIGS, BLOCKFROST_RATE_LIMIT_CONFIG, SESSION_TIMEOUT } = config();
// Important to use the same rateLimiter object for all networks,
// because Blockfrost rate limit is per IP address, not per project id
export const rateLimiter: RateLimiter = new Bottleneck({
Expand Down Expand Up @@ -60,16 +60,7 @@ export const getProviders = async (chainName: Wallet.ChainName): Promise<Wallet.
},
logger,
experiments: {
useDrepProviderOverrideActiveStatus: isExperimentEnabled(ExperimentName.USE_DREP_PROVIDER_OVERRIDE),
useWebSocket: isExperimentEnabled(ExperimentName.WEBSOCKET_API),
useBlockfrostAssetProvider: isExperimentEnabled(ExperimentName.BLOCKFROST_ASSET_PROVIDER),
useBlockfrostChainHistoryProvider: true,
useBlockfrostNetworkInfoProvider: isExperimentEnabled(ExperimentName.BLOCKFROST_NETWORK_INFO_PROVIDER),
useBlockfrostRewardsProvider: isExperimentEnabled(ExperimentName.BLOCKFROST_REWARDS_PROVIDER),
useBlockfrostTxSubmitProvider: isExperimentEnabled(ExperimentName.BLOCKFROST_TX_SUBMIT_PROVIDER),
useBlockfrostUtxoProvider: isExperimentEnabled(ExperimentName.BLOCKFROST_UTXO_PROVIDER),
useBlockfrostAddressDiscovery: true,
useBlockfrostInputResolver: true
useWebSocket: isExperimentEnabled(ExperimentName.WEBSOCKET_API)
}
});
};
Expand All @@ -93,3 +84,5 @@ export const userIdServiceProperties: RemoteApiProperties<UserIdServiceInterface
resetToDefaultValues: RemoteApiPropertyType.MethodReturningPromise,
generateWalletBasedUserId: RemoteApiPropertyType.MethodReturningPromise
};

export { SESSION_TIMEOUT };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './user-session-tracker';
export * from './is-lace-popup-open';
export * from './is-lace-tab-active';
export * from './notify-on-have-access-call';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createBackgroundMessenger } from '@cardano-sdk/web-extension';
import { logger } from '@lace/common';
import { TRACK_POPUP_CHANNEL } from '@src/utils/constants';
import { distinctUntilChanged, map, share } from 'rxjs';
import { runtime } from 'webextension-polyfill';

const channel = createBackgroundMessenger({ logger, runtime }).getChannel(TRACK_POPUP_CHANNEL);
export const isLacePopupOpen$ = channel.ports$.pipe(
map((ports) => ports.size > 0),
distinctUntilChanged(),
share()
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { LACE_EXTENSION_ID } from '@src/features/nami-migration/migration-tool/cross-extension-messaging/nami/environment';
import { distinctUntilChanged, from, fromEventPattern, map, merge, share, startWith, switchMap } from 'rxjs';
import { Tabs, tabs, windows } from 'webextension-polyfill';

type WindowId = number;

const windowRemoved$ = fromEventPattern<WindowId>(
(handler) => windows.onRemoved.addListener(handler),
(handler) => windows.onRemoved.removeListener(handler)
);
const tabUpdated$ = fromEventPattern(
(handler) => tabs.onUpdated.addListener(handler),
(handler) => tabs.onUpdated.removeListener(handler),
(tabId: number, changeInfo: Tabs.OnUpdatedChangeInfoType, tab: Tabs.Tab) => ({ tabId, changeInfo, tab })
);
const tabActivated$ = fromEventPattern<Tabs.OnActivatedActiveInfoType>(
(handler) => tabs.onActivated.addListener(handler),
(handler) => tabs.onActivated.removeListener(handler)
);

export const isLaceTabActive$ = merge(windowRemoved$, tabUpdated$, tabActivated$).pipe(
switchMap(() =>
from(
tabs.query({
active: true,
url: `chrome-extension://${LACE_EXTENSION_ID}/*`
})
)
),
map((activeLaceTabs) => activeLaceTabs.length > 0),
startWith(false),
distinctUntilChanged(),
share()
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AuthenticatorApi } from '@cardano-sdk/dapp-connector';

/**
* cip30 messaging is calling authenticator `haveAccess` on every cip30 request
* we're using it to keep track whether dapp connector is currently active
*/
export const notifyOnHaveAccessCall = (authenticator: AuthenticatorApi, notify: () => void): AuthenticatorApi => ({
requestAccess: authenticator.requestAccess.bind(authenticator),
revokeAccess: authenticator.revokeAccess.bind(authenticator),
haveAccess(...args) {
notify();
return authenticator.haveAccess(...args);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Milliseconds } from '@cardano-sdk/core';
import {
combineLatest,
distinctUntilChanged,
EMPTY,
filter,
map,
merge,
Observable,
share,
startWith,
switchMap,
timer,
Timestamp
} from 'rxjs';

export type UserSessionStatus = {
isSessionActive: boolean;
isLacePopupOpen: boolean;
isLaceTabActive: boolean;
lastDappConnectorActivityAt: Timestamp<void>;
};

export const createUserSessionTracker = (
isLacePopupOpen$: Observable<boolean>,
isLaceTabActive$: Observable<boolean>,
dAppConnectorActivity$: Observable<void>,
timeout: Milliseconds
): Observable<boolean> => {
const sharedDappConnectorActivity$ = dAppConnectorActivity$.pipe(share());
const isAnyUiActive$ = combineLatest([isLacePopupOpen$, isLaceTabActive$]).pipe(
map(([isLacePopupOpen, isLaceTabActive]) => isLacePopupOpen || isLaceTabActive),
distinctUntilChanged(),
share()
);
const on$ = merge(isAnyUiActive$.pipe(filter(Boolean)), sharedDappConnectorActivity$.pipe(map(() => true)));
const off$ = isAnyUiActive$.pipe(
switchMap((isAnyUiActive) => {
if (isAnyUiActive) {
// never go inactive while any UI is active
return EMPTY;
}
return sharedDappConnectorActivity$.pipe(
startWith(void 0),
switchMap(() => timer(timeout)),
map(() => false)
);
})
);

return merge(on$, off$).pipe(startWith(false), distinctUntilChanged());
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
/* eslint-disable unicorn/no-null */
import { runtime, storage as webStorage } from 'webextension-polyfill';
import { of, combineLatest, map, EMPTY, BehaviorSubject, Observable, from, firstValueFrom, defaultIfEmpty } from 'rxjs';
import { getProviders } from './config';
import {
of,
combineLatest,
map,
EMPTY,
BehaviorSubject,
Observable,
from,
firstValueFrom,
defaultIfEmpty,
Subject,
tap
} from 'rxjs';
import { getProviders, SESSION_TIMEOUT } from './config';
import { DEFAULT_POLLING_CONFIG, createPersonalWallet, storage, createSharedWallet } from '@cardano-sdk/wallet';
import { handleHttpProvider } from '@cardano-sdk/cardano-services-client';
import { Cardano, HandleProvider } from '@cardano-sdk/core';
Expand Down Expand Up @@ -36,6 +48,15 @@ import { ExtensionDocumentStore } from './storage/extension-document-store';
import { ExtensionBlobKeyValueStore } from './storage/extension-blob-key-value-store';
import { ExtensionBlobCollectionStore } from './storage/extension-blob-collection-store';
import { migrateCollectionStore, migrateWalletStores, shouldAttemptWalletStoresMigration } from './storage/migrations';
import { isLacePopupOpen$, createUserSessionTracker, isLaceTabActive$ } from './session';
import { TrackerSubject } from '@cardano-sdk/util-rxjs';

export const dAppConnectorActivity$ = new Subject<void>();
const pollController$ = new TrackerSubject(
createUserSessionTracker(isLacePopupOpen$, isLaceTabActive$, dAppConnectorActivity$, SESSION_TIMEOUT).pipe(
tap((isActive) => logger.debug('Session active:', isActive))
)
);

if (typeof window !== 'undefined') {
throw new TypeError('This module should only be imported in service worker');
Expand Down Expand Up @@ -77,19 +98,13 @@ type StoredWallet = AnyWallet<Wallet.WalletMetadata, Wallet.WalletMetadata>;
const createWalletRepositoryStore = (): Observable<storage.CollectionStore<StoredWallet>> =>
from(
(async () => {
// wallet repository is always using feature flag of 'mainnet', because it has to be the same for all networks
const featureFlags = await getFeatureFlags(Wallet.Cardano.ChainIds.Mainnet.networkMagic);
if (isExperimentEnabled(featureFlags, ExperimentName.EXTENSION_STORAGE)) {
const extensionStore = new ExtensionBlobCollectionStore<StoredWallet>('walletRepository', logger);
const wallets = await firstValueFrom(extensionStore.getAll().pipe(defaultIfEmpty(null)));
if (!wallets) {
const pouchdbStore = createPouchdbWalletRepositoryStore();
await migrateCollectionStore(pouchdbStore, extensionStore, logger);
}
return extensionStore;
const extensionStore = new ExtensionBlobCollectionStore<StoredWallet>('walletRepository', logger);
const wallets = await firstValueFrom(extensionStore.getAll().pipe(defaultIfEmpty(null)));
if (!wallets) {
const pouchdbStore = createPouchdbWalletRepositoryStore();
await migrateCollectionStore(pouchdbStore, extensionStore, logger);
}

return createPouchdbWalletRepositoryStore();
return extensionStore;
})()
);

Expand Down Expand Up @@ -149,6 +164,7 @@ const walletFactory: WalletFactory<Wallet.WalletMetadata, Wallet.AccountMetadata
})
: noopHandleResolver,
stores,
pollController$,
witnesser
}
);
Expand Down Expand Up @@ -196,6 +212,7 @@ const walletFactory: WalletFactory<Wallet.WalletMetadata, Wallet.AccountMetadata
})
: noopHandleResolver,
witnesser,
pollController$,
bip32Account
}
);
Expand Down
9 changes: 7 additions & 2 deletions apps/browser-extension-wallet/src/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import 'normalize.css';
import '@lace/staking/index.css';
import { BackgroundServiceAPIProvider } from '@providers/BackgroundServiceAPI';
import { ExternalLinkOpenerProvider } from '@providers/ExternalLinkOpenerProvider';
import { APP_MODE_POPUP } from './utils/constants';
import { APP_MODE_POPUP, TRACK_POPUP_CHANNEL } from './utils/constants';
import { MigrationContainer } from '@components/MigrationContainer';
import { DataCheckContainer } from '@components/DataCheckContainer';
import { PostHogClientProvider } from '@providers/PostHogClientProvider';
Expand All @@ -24,8 +24,10 @@ import { BackgroundPageProvider } from '@providers/BackgroundPageProvider';
import { AddressesDiscoveryOverlay } from 'components/AddressesDiscoveryOverlay';
import { NamiPopup } from './views/nami-mode';
import { getBackgroundStorage } from '@lib/scripts/background/storage';
import { storage } from 'webextension-polyfill';
import { runtime, storage } from 'webextension-polyfill';
import { NamiMigrationGuard } from './features/nami-migration/NamiMigrationGuard';
import { createNonBackgroundMessenger } from '@cardano-sdk/web-extension';
import { logger } from '@lib/wallet-api-ui';

const App = (): React.ReactElement => {
const [mode, setMode] = useState<'lace' | 'nami'>();
Expand Down Expand Up @@ -87,3 +89,6 @@ const App = (): React.ReactElement => {

const mountNode = document.querySelector('#lace-popup');
ReactDOM.render(<App />, mountNode);

// not exposing any API; used to keep track of connection with SW to determine whether popup is open
createNonBackgroundMessenger({ baseChannel: TRACK_POPUP_CHANNEL }, { logger, runtime });
Loading
Loading