Skip to content

Commit

Permalink
wallet-ext: add hasPermissions and requestPermissions to dapp interface
Browse files Browse the repository at this point in the history
  • Loading branch information
pchrysochoidis committed Jul 1, 2022
1 parent 1c6eae3 commit b2d9e8d
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 20 deletions.
8 changes: 4 additions & 4 deletions wallet/src/background/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Permissions {
private _permissionResponses: Subject<PermissionResponse> = new Subject();

public async acquirePermissions(
permissionTypes: PermissionType[],
permissionTypes: readonly PermissionType[],
connection: ContentScriptConnection
): Promise<Permission> {
const { origin } = connection;
Expand Down Expand Up @@ -127,7 +127,7 @@ class Permissions {

public async hasPermissions(
origin: string,
permissionTypes: PermissionType[],
permissionTypes: readonly PermissionType[],
permission?: Permission | null
): Promise<boolean> {
const existingPermission = await this.getPermission(origin, permission);
Expand All @@ -142,7 +142,7 @@ class Permissions {

private async createPermissionRequest(
origin: string,
permissionTypes: PermissionType[],
permissionTypes: readonly PermissionType[],
favIcon: string | undefined,
existingPermission?: Permission | null
): Promise<Permission> {
Expand All @@ -164,7 +164,7 @@ class Permissions {
createdDate: new Date().toISOString(),
origin,
favIcon,
permissions: permissionTypes,
permissions: permissionTypes as PermissionType[],
responseDate: null,
};
}
Expand Down
57 changes: 55 additions & 2 deletions wallet/src/background/connections/ContentScriptConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
import { Connection } from './Connection';
import { createMessage } from '_messages';
import { isGetAccount } from '_payloads/account/GetAccount';
import {
isAcquirePermissionsRequest,
isHasPermissionRequest,
} from '_payloads/permissions';
import Permissions from '_src/background/Permissions';

import type { SuiAddress } from '@mysten/sui.js';
import type { Message } from '_messages';
import type { PortChannelName } from '_messaging/PortChannelName';
import type { ErrorPayload } from '_payloads';
import type { GetAccountResponse } from '_payloads/account/GetAccountResponse';
import type {
HasPermissionsResponse,
AcquirePermissionsResponse,
} from '_payloads/permissions';
import type { Runtime } from 'webextension-polyfill';

export class ContentScriptConnection extends Connection {
Expand All @@ -28,12 +36,57 @@ export class ContentScriptConnection extends Connection {
protected async handleMessage(msg: Message) {
const { payload } = msg;
if (isGetAccount(payload)) {
const existingPermission = await Permissions.getPermission(
this.origin
);
if (
!(await Permissions.hasPermissions(
this.origin,
['viewAccount'],
existingPermission
)) ||
!existingPermission
) {
this.sendError(
{
error: true,
message:
"Operation not allowed, dapp doesn't have the required permissions",
code: -2,
},
msg.id
);
} else {
this.sendAccounts(existingPermission.accounts, msg.id);
}
} else if (isHasPermissionRequest(payload)) {
this.send(
createMessage<HasPermissionsResponse>(
{
type: 'has-permissions-response',
result: await Permissions.hasPermissions(
this.origin,
payload.permissions
),
},
msg.id
)
);
} else if (isAcquirePermissionsRequest(payload)) {
try {
const permission = await Permissions.acquirePermissions(
['viewAccount'],
payload.permissions,
this
);
this.sendAccounts(permission.accounts, msg.id);
this.send(
createMessage<AcquirePermissionsResponse>(
{
type: 'acquire-permissions-response',
result: !!permission.allowed,
},
msg.id
)
);
} catch (e) {
this.sendError(
{
Expand Down
67 changes: 55 additions & 12 deletions wallet/src/dapp-interface/DAppInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,39 @@ import { filter, lastValueFrom, map, take } from 'rxjs';
import { createMessage } from '_messages';
import { WindowMessageStream } from '_messaging/WindowMessageStream';
import { isErrorPayload } from '_payloads';
import { ALL_PERMISSION_TYPES } from '_payloads/permissions';

import type { SuiAddress } from '@mysten/sui.js';
import type { Payload } from '_payloads';
import type { GetAccount } from '_payloads/account/GetAccount';
import type { GetAccountResponse } from '_payloads/account/GetAccountResponse';
import type {
PermissionType,
HasPermissionsRequest,
HasPermissionsResponse,
AcquirePermissionsRequest,
AcquirePermissionsResponse,
} from '_payloads/permissions';
import type { Observable } from 'rxjs';

function mapToPromise<T extends Payload, R>(
stream: Observable<T>,
project: (value: T) => R
) {
return lastValueFrom(
stream.pipe(
take<T>(1),
map<T, R>((response) => {
if (isErrorPayload(response)) {
// TODO: throw proper error
throw new Error(response.message);
}
return project(response);
})
)
);
}

export class DAppInterface {
private _messagesStream: WindowMessageStream;

Expand All @@ -23,20 +49,37 @@ export class DAppInterface {
);
}

public hasPermissions(
permissions: readonly PermissionType[] = ALL_PERMISSION_TYPES
): Promise<boolean> {
return mapToPromise(
this.send<HasPermissionsRequest, HasPermissionsResponse>({
type: 'has-permissions-request',
permissions,
}),
(response) => response.result
);
}

public requestPermissions(
permissions: readonly PermissionType[] = ALL_PERMISSION_TYPES
): Promise<boolean> {
return mapToPromise(
this.send<AcquirePermissionsRequest, AcquirePermissionsResponse>({
type: 'acquire-permissions-request',
permissions,
}),
(response) => response.result
);
}

public getAccounts(): Promise<SuiAddress[]> {
const stream = this.send<GetAccount, GetAccountResponse>({
type: 'get-account',
}).pipe(
take(1),
map((response) => {
if (isErrorPayload(response)) {
// TODO: throw proper error
throw new Error(response.message);
}
return response.accounts;
})
return mapToPromise(
this.send<GetAccount, GetAccountResponse>({
type: 'get-account',
}),
(response) => response.accounts
);
return lastValueFrom(stream);
}

private send<
Expand Down
6 changes: 5 additions & 1 deletion wallet/src/shared/messaging/messages/payloads/BasePayload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export type PayloadType =
| 'permission-response'
| 'get-permission-requests'
| 'get-account'
| 'get-account-response';
| 'get-account-response'
| 'has-permissions-request'
| 'has-permissions-response'
| 'acquire-permissions-request'
| 'acquire-permissions-response';

export interface BasePayload {
type: PayloadType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isBasePayload } from '_payloads';

import type { PermissionType } from './PermissionType';
import type { BasePayload, Payload } from '_payloads';

export interface AcquirePermissionsRequest extends BasePayload {
type: 'acquire-permissions-request';
permissions: readonly PermissionType[];
}

export function isAcquirePermissionsRequest(
payload: Payload
): payload is AcquirePermissionsRequest {
return (
isBasePayload(payload) && payload.type === 'acquire-permissions-request'
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isBasePayload } from '_payloads';

import type { BasePayload, Payload } from '_payloads';

export interface AcquirePermissionsResponse extends BasePayload {
type: 'acquire-permissions-response';
result: boolean;
}

export function isAcquirePermissionsResponse(
payload: Payload
): payload is AcquirePermissionsResponse {
return (
isBasePayload(payload) &&
payload.type === 'acquire-permissions-response'
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isBasePayload } from '_payloads';

import type { PermissionType } from './PermissionType';
import type { BasePayload, Payload } from '_payloads';

export interface HasPermissionsRequest extends BasePayload {
type: 'has-permissions-request';
permissions: readonly PermissionType[];
}

export function isHasPermissionRequest(
payload: Payload
): payload is HasPermissionsRequest {
return isBasePayload(payload) && payload.type === 'has-permissions-request';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { isBasePayload } from '_payloads';

import type { BasePayload, Payload } from '_payloads';

export interface HasPermissionsResponse extends BasePayload {
type: 'has-permissions-response';
result: boolean;
}

export function isHasPermissionResponse(
payload: Payload
): payload is HasPermissionsResponse {
return (
isBasePayload(payload) && payload.type === 'has-permissions-response'
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export type PermissionType = 'viewAccount' | 'suggestTransactions';
export const ALL_PERMISSION_TYPES = [
'viewAccount',
'suggestTransactions',
] as const;
type AllPermissionsType = typeof ALL_PERMISSION_TYPES;
export type PermissionType = AllPermissionsType[number];
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export * from './PermissionRequests';
export * from './PermissionResponse';
export * from './PermissionType';
export * from './Permission';
export * from './HasPermissionsRequest';
export * from './HasPermissionsResponse';
export * from './AcquirePermissionsRequest';
export * from './AcquirePermissionsResponse';

0 comments on commit b2d9e8d

Please sign in to comment.