From 54a9165dba9f13df9e6ad2fb36ea790cd32a51af Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 9 Oct 2024 16:32:25 +0200 Subject: [PATCH] wip: Add DeviceController --- .../src/devices/DeviceController.ts | 90 +++++++++++++++++++ .../snaps-controllers/src/devices/index.ts | 1 + packages/snaps-controllers/src/index.ts | 1 + .../src/permitted/handlers.ts | 3 + .../src/permitted/requestDevice.ts | 65 ++++++++++++++ 5 files changed, 160 insertions(+) create mode 100644 packages/snaps-controllers/src/devices/DeviceController.ts create mode 100644 packages/snaps-controllers/src/devices/index.ts create mode 100644 packages/snaps-rpc-methods/src/permitted/requestDevice.ts diff --git a/packages/snaps-controllers/src/devices/DeviceController.ts b/packages/snaps-controllers/src/devices/DeviceController.ts new file mode 100644 index 0000000000..5009a20efe --- /dev/null +++ b/packages/snaps-controllers/src/devices/DeviceController.ts @@ -0,0 +1,90 @@ +import type { + RestrictedControllerMessenger, + ControllerGetStateAction, + ControllerStateChangeEvent, +} from '@metamask/base-controller'; +import { BaseController } from '@metamask/base-controller'; +import type { GetPermissions } from '@metamask/permission-controller'; + +import type { DeleteInterface } from '../interface'; +import type { GetAllSnaps, HandleSnapRequest } from '../snaps'; +import type { + TransactionControllerUnapprovedTransactionAddedEvent, + SignatureStateChange, + TransactionControllerTransactionStatusUpdatedEvent, +} from '../types'; + +const controllerName = 'DeviceController'; + +export type DeviceControllerAllowedActions = + | HandleSnapRequest + | GetAllSnaps + | GetPermissions + | DeleteInterface; + +export type DeviceControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + DeviceControllerState +>; + +export type DeviceControllerActions = DeviceControllerGetStateAction; + +export type DeviceControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + DeviceControllerState +>; + +export type DeviceControllerEvents = DeviceControllerStateChangeEvent; + +export type DeviceControllerAllowedEvents = + | TransactionControllerUnapprovedTransactionAddedEvent + | TransactionControllerTransactionStatusUpdatedEvent + | SignatureStateChange; + +export type DeviceControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + DeviceControllerActions | DeviceControllerAllowedActions, + DeviceControllerEvents | DeviceControllerAllowedEvents, + DeviceControllerAllowedActions['type'], + DeviceControllerAllowedEvents['type'] +>; + +export type Device = {}; + +export type DeviceControllerState = { + devices: Record; +}; + +export type DeviceControllerArgs = { + messenger: DeviceControllerMessenger; + state?: DeviceControllerState; +}; +/** + * Controller for managing access to devices for Snaps. + */ +export class DeviceController extends BaseController< + typeof controllerName, + DeviceControllerState, + DeviceControllerMessenger +> { + constructor({ messenger, state }: DeviceControllerArgs) { + super({ + messenger, + metadata: { + devices: { persist: true, anonymous: false }, + }, + name: controllerName, + state: { ...state, devices: {} }, + }); + } + + async requestDevices() { + const devices = await (navigator as any).hid.requestDevice({ filters: [] }); + return devices; + } + + async getDevices() { + const devices = await (navigator as any).hid.getDevices(); + return devices; + } +} diff --git a/packages/snaps-controllers/src/devices/index.ts b/packages/snaps-controllers/src/devices/index.ts new file mode 100644 index 0000000000..1e49eb0e35 --- /dev/null +++ b/packages/snaps-controllers/src/devices/index.ts @@ -0,0 +1 @@ +export * from './DeviceController'; diff --git a/packages/snaps-controllers/src/index.ts b/packages/snaps-controllers/src/index.ts index 46f68a7381..daeabcbf83 100644 --- a/packages/snaps-controllers/src/index.ts +++ b/packages/snaps-controllers/src/index.ts @@ -5,3 +5,4 @@ export * from './utils'; export * from './cronjob'; export * from './interface'; export * from './insights'; +export * from './devices'; diff --git a/packages/snaps-rpc-methods/src/permitted/handlers.ts b/packages/snaps-rpc-methods/src/permitted/handlers.ts index 5bfacdacd4..37b0771190 100644 --- a/packages/snaps-rpc-methods/src/permitted/handlers.ts +++ b/packages/snaps-rpc-methods/src/permitted/handlers.ts @@ -1,5 +1,6 @@ import { createInterfaceHandler } from './createInterface'; import { providerRequestHandler } from './experimentalProviderRequest'; +import { providerRequestHandler as requestDeviceHandler } from './requestDevice'; import { getAllSnapsHandler } from './getAllSnaps'; import { getClientStatusHandler } from './getClientStatus'; import { getCurrencyRateHandler } from './getCurrencyRate'; @@ -12,6 +13,7 @@ import { requestSnapsHandler } from './requestSnaps'; import { resolveInterfaceHandler } from './resolveInterface'; import { updateInterfaceHandler } from './updateInterface'; + /* eslint-disable @typescript-eslint/naming-convention */ export const methodHandlers = { wallet_getAllSnaps: getAllSnapsHandler, @@ -27,6 +29,7 @@ export const methodHandlers = { snap_resolveInterface: resolveInterfaceHandler, snap_getCurrencyRate: getCurrencyRateHandler, snap_experimentalProviderRequest: providerRequestHandler, + snap_requestDevice: requestDeviceHandler, }; /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/packages/snaps-rpc-methods/src/permitted/requestDevice.ts b/packages/snaps-rpc-methods/src/permitted/requestDevice.ts new file mode 100644 index 0000000000..e49c542bb2 --- /dev/null +++ b/packages/snaps-rpc-methods/src/permitted/requestDevice.ts @@ -0,0 +1,65 @@ +import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; +import type { PermittedHandlerExport } from '@metamask/permission-controller'; +import type { + JsonRpcRequest, + ProviderRequestParams, + ProviderRequestResult, +} from '@metamask/snaps-sdk'; +import { type InferMatching } from '@metamask/snaps-utils'; +import { object, optional, string, type } from '@metamask/superstruct'; +import { + type PendingJsonRpcResponse, + CaipChainIdStruct, + JsonRpcParamsStruct, +} from '@metamask/utils'; + +import type { MethodHooksObject } from '../utils'; + +const hookNames: MethodHooksObject = { + requestDevices: true, +}; + +export type ProviderRequestMethodHooks = { + requestDevices: () => any; +}; + +export const providerRequestHandler: PermittedHandlerExport< + ProviderRequestMethodHooks, + ProviderRequestParameters, + ProviderRequestResult +> = { + methodNames: ['snap_requestDevice'], + implementation: providerRequestImplementation, + hookNames, +}; + +const ProviderRequestParametersStruct = object({ + chainId: CaipChainIdStruct, + request: type({ + method: string(), + params: optional(JsonRpcParamsStruct), + }), +}); + +export type ProviderRequestParameters = InferMatching< + typeof ProviderRequestParametersStruct, + ProviderRequestParams +>; + +async function providerRequestImplementation( + req: JsonRpcRequest, + res: PendingJsonRpcResponse, + _next: unknown, + end: JsonRpcEngineEndCallback, + { requestDevices }: ProviderRequestMethodHooks, +): Promise { + const { params } = req; + + try { + res.result = await requestDevices(); + } catch (error) { + return end(error); + } + + return end(); +}