Skip to content

Commit

Permalink
wallet-ext: connect site view
Browse files Browse the repository at this point in the history
* allows the user to accept/reject a dapp connection
* using mock permissions for now and background client
  • Loading branch information
pchrysochoidis committed Jun 24, 2022
1 parent 36d3c32 commit ec299ae
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 25 deletions.
4 changes: 3 additions & 1 deletion wallet/configs/ts/tsconfig.common.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"_store": ["./src/ui/app/redux/store/"],
"_store/*": ["./src/ui/app/redux/store/*"],
"_hooks": ["./src/ui/app/hooks/"],
"_components/*": ["./src/ui/app/components/*"]
"_components/*": ["./src/ui/app/components/*"],
"_messaging/*": ["./src/shared/messaging/*"],
"_messages/*": ["./src/shared/messaging/messages/*"]
}
},
"include": ["../../src"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import type { PermissionType } from './PermissionType';
import type { SuiAddress } from '@mysten/sui.js';

export interface Permission {
id: string;
origin: string;
favIcon: string | undefined;
accounts: SuiAddress[];
allowed: boolean | null;
permissions: PermissionType[];
createdDate: string;
responseDate: string | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export type PermissionType = 'viewAccount';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export * from './PermissionType';
export * from './Permission';
52 changes: 52 additions & 0 deletions wallet/src/ui/app/background-client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { setPermissions } from '_redux/slices/permissions';

import type { SuiAddress } from '@mysten/sui.js';
import type { AppDispatch } from '_store';

export class BackgroundClient {
private _dispatch: AppDispatch | null = null;
private _initialized = false;

public async init(dispatch: AppDispatch) {
if (this._initialized) {
throw new Error('[BackgroundClient] already initialized');
}
this._initialized = true;
this._dispatch = dispatch;
// TODO: implement
return this.sendGetPermissionRequests().then(() => undefined);
}

public sendPermissionResponse(
id: string,
accounts: SuiAddress[],
allowed: boolean,
responseDate: string
) {
// TODO: implement
}

public async sendGetPermissionRequests() {
// TODO: remove mock and implement
const id = /connect\/(.+)/.exec(window.location.hash)?.[1];
if (this._dispatch && id) {
this._dispatch(
setPermissions([
{
id,
accounts: [],
allowed: null,
createdDate: new Date().toISOString(),
favIcon: 'https://www.google.com/favicon.ico',
origin: 'https://www.google.com',
permissions: ['viewAccount'],
responseDate: null,
},
])
);
}
}
}
25 changes: 17 additions & 8 deletions wallet/src/ui/app/components/account-address/index.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import cl from 'classnames';

import CopyToClipboard from '_components/copy-to-clipboard';
import ExplorerLink from '_components/explorer-link';
import { ExplorerLinkType } from '_components/explorer-link/ExplorerLinkType';
import { useAppSelector, useMiddleEllipsis } from '_hooks';

import st from './AccountAddress.module.scss';

function AccountAddress() {
type AccountAddressProps = {
className?: string;
showLink?: boolean;
};

function AccountAddress({ className, showLink = true }: AccountAddressProps) {
const address = useAppSelector(
({ account: { address } }) => address && `0x${address}`
);
const shortenAddress = useMiddleEllipsis(address || '', 20);
return address ? (
<span className={st['address-container']}>
<span className={cl(st.addressContainer, className)}>
<CopyToClipboard txt={address}>
<span className={st.address} title={address}>
{shortenAddress}
</span>
</CopyToClipboard>
<ExplorerLink
type={ExplorerLinkType.address}
useActiveAddress={true}
title="View account on Sui Explorer"
className={st.explorerLink}
/>
{showLink ? (
<ExplorerLink
type={ExplorerLinkType.address}
useActiveAddress={true}
title="View account on Sui Explorer"
className={st.explorerLink}
/>
) : null}
</span>
) : null;
}
Expand Down
2 changes: 2 additions & 0 deletions wallet/src/ui/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import BackupPage from './pages/initialize/backup';
import CreatePage from './pages/initialize/create';
import ImportPage from './pages/initialize/import';
import SelectPage from './pages/initialize/select';
import SiteConnectPage from './pages/site-connect';
import TransactionDetailsPage from './pages/transaction-details';
import TransferCoinPage from './pages/transfer-coin';
import WelcomePage from './pages/welcome';
Expand Down Expand Up @@ -56,6 +57,7 @@ const App = () => {
<Route path="import" element={<ImportPage />} />
<Route path="backup" element={<BackupPage />} />
</Route>
<Route path="/connect/:requestID" element={<SiteConnectPage />} />
<Route
path="*"
element={<Navigate to="/tokens" replace={true} />}
Expand Down
58 changes: 58 additions & 0 deletions wallet/src/ui/app/pages/site-connect/SiteConnectPage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.container {
display: flex;
flex-flow: column nowrap;
padding: 15px;
align-items: center;
justify-content: center;
}

.title {
margin-bottom: 30px;
}

.origin-container {
display: flex;
flex-flow: column nowrap;
align-items: center;
padding: 8px;
border: 1px solid #b2bbc3;
border-radius: 4px;
align-self: stretch;
}

.fav-icon {
width: 40px;
border-radius: 4px;
margin-bottom: 8px;
}

.origin {
font-size: 14px;
font-weight: 500;
}

.label {
align-self: flex-start;
font-weight: 700;
margin-top: 16px;
margin-bottom: 2px;
letter-spacing: 0.6px;
}

.permission {
font-weight: 500;
font-style: italic;
padding: 3px 6px;
background-color: #c7c7c7;
border-radius: 4px;
display: inline-block;
}

.actions {
display: flex;
flex-flow: row nowrap;
margin-top: 35px;
align-self: stretch;
align-items: center;
justify-content: space-between;
}
122 changes: 122 additions & 0 deletions wallet/src/ui/app/pages/site-connect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useCallback, useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';

import AccountAddress from '_components/account-address';
import Loading from '_components/loading';
import { useAppDispatch, useAppSelector, useInitializedGuard } from '_hooks';
import {
permissionsSelectors,
respondToPermissionRequest,
} from '_redux/slices/permissions';

import type { PermissionType } from '_messages/payloads/permissions';
import type { RootState } from '_redux/RootReducer';
import type { MouseEventHandler } from 'react';

import st from './SiteConnectPage.module.scss';

const permissionTypeToTxt: Record<PermissionType, string> = {
viewAccount: 'View Account',
};

function SiteConnectPage() {
const { requestID } = useParams();
const guardLoading = useInitializedGuard(true);
const permissionsInitialized = useAppSelector(
({ permissions }) => permissions.initialized
);
const loading = guardLoading || !permissionsInitialized;
const permissionSelector = useMemo(
() => (state: RootState) =>
requestID
? permissionsSelectors.selectById(state, requestID)
: null,
[requestID]
);
const dispatch = useAppDispatch();
const permissionRequest = useAppSelector(permissionSelector);
const activeAccount = useAppSelector(({ account }) => account.address);
const [submitting, setSubmitting] = useState(false);
const handleOnResponse = useCallback<MouseEventHandler<HTMLButtonElement>>(
(e) => {
const allowed = e.currentTarget.dataset.allow === 'true';
if (requestID && activeAccount) {
setSubmitting(true);
dispatch(
respondToPermissionRequest({
id: requestID,
accounts: allowed ? [activeAccount] : [],
allowed,
})
);
}
},
[dispatch, requestID, activeAccount]
);
useEffect(() => {
if (
!loading &&
(!permissionRequest || permissionRequest.responseDate)
) {
window.close();
}
}, [loading, permissionRequest]);

return (
<Loading loading={loading}>
{permissionRequest ? (
<div className={st.container}>
<h2 className={st.title}>Connect to Sui wallet</h2>
<label className={st.label}>Site</label>
<div className={st.originContainer}>
{permissionRequest.favIcon ? (
<img
className={st.favIcon}
src={permissionRequest.favIcon}
alt="Site favicon"
/>
) : null}
<span className={st.origin}>
{permissionRequest.origin}
</span>
</div>
<label className={st.label}>Account</label>
<AccountAddress showLink={false} />
<label className={st.label}>Permissions</label>
<div className={st.permissionsContainer}>
{permissionRequest.permissions.map((aPermission) => (
<span className={st.permission} key={aPermission}>
{permissionTypeToTxt[aPermission]}
</span>
))}
</div>
<div className={st.actions}>
<button
type="button"
data-allow="false"
onClick={handleOnResponse}
className="btn link"
disabled={submitting}
>
Cancel
</button>
<button
type="button"
className="btn"
data-allow="true"
onClick={handleOnResponse}
disabled={submitting}
>
Connect
</button>
</div>
</div>
) : null}
</Loading>
);
}

export default SiteConnectPage;
2 changes: 2 additions & 0 deletions wallet/src/ui/app/redux/RootReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { combineReducers } from '@reduxjs/toolkit';

import account from './slices/account';
import app from './slices/app';
import permissions from './slices/permissions';
import suiObjects from './slices/sui-objects';
import transactions from './slices/transactions';
import txresults from './slices/txresults';
Expand All @@ -15,6 +16,7 @@ const rootReducer = combineReducers({
suiObjects,
transactions,
txresults,
permissions,
});

export type RootState = ReturnType<typeof rootReducer>;
Expand Down
Loading

0 comments on commit ec299ae

Please sign in to comment.