Skip to content

Commit

Permalink
refactor: redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
fbwoolf committed Jun 24, 2021
1 parent 00033ae commit 565eb25
Show file tree
Hide file tree
Showing 12 changed files with 37 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .changeset/wicked-mails-grab.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
'@stacks/wallet-web': minor
---

This adds using the app url to redirect and reload the app after the extension is installed.
This adds a redirect to the app installing the extension after a user sets their password.
2 changes: 1 addition & 1 deletion scripts/generate-manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const manifest = {
author: 'Hiro PBC',
description:
'Stacks Wallet. Use the Stacks blockchain to access privacy-friendly apps, and keep data in your control.',
permissions: ['tabs'],
permissions: ['tabs', 'storage'],
manifest_version: 2,
background: {
scripts: ['background.js'],
Expand Down
52 changes: 14 additions & 38 deletions src/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
CONTENT_SCRIPT_PORT,
ExternalMethods,
MessageFromContentScript,
InternalMethods,
MESSAGE_SOURCE,
} from '@common/message-types';

import type { VaultActions } from '@background/vault-types';
Expand All @@ -21,31 +19,19 @@ import { vaultMessageHandler } from '@background/vault';

const IS_TEST_ENV = process.env.TEST_ENV === 'true';

function setAppParamOfSourceInstallingExtension() {
// The app url is saved for use after onboarding
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const appUrl = urlParams.get('app');
// Left for testing only
// const appUrl = 'http://localhost:3000/';
if (appUrl) window.localStorage.setItem('appUrl', appUrl);
}

function getAppParamOfSourceInstallingExtension() {
const appUrl = window.localStorage.getItem('appUrl') || undefined;
return appUrl;
}
const CHROME_STORE_URL =
'https://chrome.google.com/webstore/detail/stacks-wallet/ldinpeekobnhjjdofggfgjlcehhmanlj/';
const FIREFOX_STORE_URL = 'https://addons.mozilla.org/en-US/firefox/addon/stacks-wallet/';

// Listen for install event
chrome.runtime.onInstalled.addListener(async details => {
if (details.reason === 'install' && !IS_TEST_ENV) {
setAppParamOfSourceInstallingExtension();
const appUrl = getAppParamOfSourceInstallingExtension();
// Reload the app tab right after install to detect the extension
chrome.tabs.query({ lastFocusedWindow: true }, async tabs => {
const appTab = tabs?.find(tab => tab.url === appUrl);
const appTab = tabs?.find(
tab => tab.url === CHROME_STORE_URL || tab.url === FIREFOX_STORE_URL
);
if (appTab?.id) {
await chrome.tabs.reload(appTab?.id);
await chrome.tabs.remove(appTab.id);
}
});
await chrome.tabs.create({
Expand Down Expand Up @@ -92,22 +78,12 @@ chrome.runtime.onConnect.addListener(port => {
}
});

function redirectToAppTab() {
const appUrl = getAppParamOfSourceInstallingExtension();
function goBackToApp() {
chrome.tabs.query({ lastFocusedWindow: true }, async tabs => {
const appTab = tabs?.find(tab => tab.url === appUrl);
if (appTab?.id) {
await chrome.tabs.update(appTab?.id, { active: true });
chrome.tabs.sendMessage(appTab?.id, {
source: MESSAGE_SOURCE,
method: InternalMethods.completeOnboarding,
payload: undefined,
});
} else {
// Only open a new tab if the user has closed the app tab
await chrome.tabs.create({
url: appUrl,
});
const appTabId = tabs[tabs.length - 2].id;
if (appTabId) {
await chrome.tabs.update(appTabId, { active: true });
await chrome.tabs.reload(appTabId);
}
});
}
Expand All @@ -117,9 +93,9 @@ chrome.runtime.onMessage.addListener((message: VaultActions, sender, sendRespons
// Only respond to internal messages from our UI, not content scripts in other applications
if (!sender.url?.startsWith(chrome.runtime.getURL(''))) return;
// Go back to app tab after setting password
if (message.method === 'completeOnboarding') {
if (message.method === 'redirectAfterSetPassword') {
// Delay redirect for a smoother transition
setTimeout(() => redirectToAppTab(), 500);
setTimeout(() => goBackToApp(), 500);
}
void vaultMessageHandler(message).then(sendResponse).catch(sendResponse);
// Return true to specify that we are responding async
Expand Down
7 changes: 5 additions & 2 deletions src/background/vault-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export type SetPassword = VaultMessage<InternalMethods.setPassword, string>;
export type UnlockWallet = VaultMessage<InternalMethods.unlockWallet, string>;
export type LockWallet = VaultMessage<InternalMethods.lockWallet, undefined>;
export type SwitchAccount = VaultMessage<InternalMethods.switchAccount, number>;
export type CompleteOnboarding = VaultMessage<InternalMethods.completeOnboarding, undefined>;
export type RedirectAfterSetPassword = VaultMessage<
InternalMethods.redirectAfterSetPassword,
undefined
>;

export type VaultActions =
| GetWallet
Expand All @@ -29,4 +32,4 @@ export type VaultActions =
| UnlockWallet
| SwitchAccount
| LockWallet
| CompleteOnboarding;
| RedirectAfterSetPassword;
2 changes: 1 addition & 1 deletion src/background/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function throwUnhandledMethod(message: VaultActions) {
// Reducer to manage the state of the vault
export const vaultReducer = async (message: VaultActions): Promise<InMemoryVault> => {
switch (message.method) {
case InternalMethods.completeOnboarding:
case InternalMethods.redirectAfterSetPassword:
return {
...inMemoryVault,
hasCompletedOnboarding: true,
Expand Down
2 changes: 1 addition & 1 deletion src/common/hooks/use-vault-messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const useVaultMessenger = () => {

const doCompleteOnboarding = () =>
innerMessageWrapper({
method: InternalMethods.completeOnboarding,
method: InternalMethods.redirectAfterSetPassword,
payload: undefined,
});

Expand Down
4 changes: 2 additions & 2 deletions src/common/message-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export enum InternalMethods {
unlockWallet = 'unlockWallet',
lockWallet = 'lockWallet',
switchAccount = 'switchAccount',
completeOnboarding = 'completeOnboarding',
redirectAfterSetPassword = 'redirectAfterSetPassword',
}

export type ExtensionMethods = ExternalMethods | InternalMethods;
Expand Down Expand Up @@ -63,7 +63,7 @@ export type TransactionResponseMessage = Message<
>;

export type CompleteOnboardingResponseMessage = Message<
InternalMethods.completeOnboarding,
InternalMethods.redirectAfterSetPassword,
undefined
>;

Expand Down
2 changes: 1 addition & 1 deletion src/pages/set-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const SetPasswordPage: React.FC<SetPasswordProps> = ({
if (strengthResult.meetsAllStrengthRequirements) {
await submit(password);
// Sends message to background script
void doCompleteOnboarding();
await doCompleteOnboarding();
return;
}
setLoading(false);
Expand Down
4 changes: 1 addition & 3 deletions test-app/src/common/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { UserSession, AppConfig, UserData } from '@stacks/auth';

export interface AppState {
userData: UserData | null;
isOnboarding: boolean;
}

export const defaultState = (): AppState => {
Expand All @@ -13,10 +12,9 @@ export const defaultState = (): AppState => {
if (userSession.isUserSignedIn()) {
return {
userData: userSession.loadUserData(),
isOnboarding: false,
};
}
return { userData: null, isOnboarding: false };
return { userData: null };
};

export const AppContext = createContext<AppState>(defaultState());
15 changes: 6 additions & 9 deletions test-app/src/common/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { AppState, defaultState } from '@common/context';
import { AppConfig, UserSession } from '@stacks/auth';
import { AuthOptions } from '@stacks/connect';

export function useAuth() {
const [state, setState] = useState<AppState>(defaultState());
const [state, setState] = React.useState<AppState>(defaultState());
const [authResponse, setAuthResponse] = React.useState('');
const [appPrivateKey, setAppPrivateKey] = React.useState('');

Expand All @@ -14,17 +14,15 @@ export function useAuth() {
);
const userSession = useMemo(() => new UserSession({ appConfig }), [appConfig]);

const handleIsOnboarding = (isOnboarding: boolean) => setState({ ...state, isOnboarding });

const handleSignOut = useCallback(() => {
userSession.signUserOut();
setState({ ...state, userData: null, isOnboarding: false });
setState({ userData: null });
}, [userSession]);

const handleRedirectAuth = useCallback(async () => {
if (userSession.isSignInPending()) {
const userData = await userSession.handlePendingSignIn();
setState({ ...state, userData, isOnboarding: false });
setState({ userData });
setAppPrivateKey(userData.appPrivateKey);
} else if (userSession.isUserSignedIn()) {
setAppPrivateKey(userSession.loadUserData().appPrivateKey);
Expand All @@ -35,7 +33,7 @@ export function useAuth() {
const userData = userSession.loadUserData();
setAppPrivateKey(userSession.loadUserData().appPrivateKey);
setAuthResponse(authResponse);
setState({ ...state, userData, isOnboarding: false });
setState({ userData });
}, []);

const onCancel = useCallback(() => {
Expand All @@ -46,7 +44,7 @@ export function useAuth() {
void handleRedirectAuth();
if (userSession.isUserSignedIn() && !state.userData) {
const userData = userSession.loadUserData();
setState({ ...state, userData, isOnboarding: false });
setState({ userData });
}
}, [handleRedirectAuth, userSession, state]);

Expand All @@ -67,6 +65,5 @@ export function useAuth() {
authResponse,
appPrivateKey,
handleSignOut,
handleIsOnboarding,
};
}
16 changes: 2 additions & 14 deletions test-app/src/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { ThemeProvider, theme, Flex, ColorModeProvider } from '@stacks/ui';
import { Connect } from '@stacks/connect-react';
import { AppContext } from '@common/context';
Expand All @@ -9,19 +9,7 @@ import { useAuth } from '@common/use-auth';
import { GlobalStyles } from '@components/global-styles';

export const App: React.FC = () => {
const { authOptions, state, authResponse, appPrivateKey, handleSignOut, handleIsOnboarding } =
useAuth();

const handleMessage = (event: MessageEvent) => {
if (event.data.method === 'completeOnboarding') {
handleIsOnboarding(true);
}
};

useEffect(() => {
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
const { authOptions, state, authResponse, appPrivateKey, handleSignOut } = useAuth();

return (
<ThemeProvider theme={theme}>
Expand Down
7 changes: 2 additions & 5 deletions test-app/src/components/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import React, { useContext } from 'react';
import React from 'react';
import { Button, Text, Box, ButtonGroup } from '@stacks/ui';
import { useConnect } from '@stacks/connect-react';
import { AppContext } from '@common/context';

export const Auth: React.FC = () => {
const state = useContext(AppContext);
const { doOpenAuth } = useConnect();

return (
<Box>
<Text display="block" textStyle="body.large">
Sign in with your Stacks Wallet to try out a demo of the Stacks 2.0 blockchain.
</Text>
<ButtonGroup spacing={'base'} mt={'base-loose'}>
<Button size="lg" mode="primary" onClick={() => doOpenAuth()} data-test="sign-up">
{state.isOnboarding ? 'Choose Account' : 'Sign up'}
Sign up
</Button>
</ButtonGroup>
</Box>
Expand Down

0 comments on commit 565eb25

Please sign in to comment.