Skip to content

Commit

Permalink
refactor(rpc): register rpc request handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Mar 5, 2025
1 parent 6e753a5 commit 8e4169a
Show file tree
Hide file tree
Showing 20 changed files with 1,794 additions and 1,412 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
"are-passive-events-supported": "1.1.1",
"argon2-browser": "1.18.0",
"assert": "2.1.0",
"axios": "1.7.7",
"axios": "1.8.1",
"base64url": "3.0.1",
"bignumber.js": "9.1.2",
"bitcoin-address-validation": "2.2.1",
Expand Down Expand Up @@ -275,8 +275,8 @@
"@pandacss/dev": "0.46.1",
"@playwright/test": "1.50.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.13",
"@redux-devtools/cli": "4.0.0",
"@redux-devtools/remote": "0.9.3",
"@redux-devtools/cli": "4.0.2",
"@redux-devtools/remote": "0.9.5",
"@schemastore/web-manifest": "0.0.6",
"@sentry/react": "8.26.0",
"@sentry/webpack-plugin": "2.17.0",
Expand Down Expand Up @@ -335,7 +335,7 @@
"deepmerge": "4.3.1",
"dependency-cruiser": "16.4.2",
"dotenv-webpack": "8.1.0",
"esbuild": "0.24.0",
"esbuild": "0.25.0",
"esbuild-loader": "4.2.2",
"eslint-plugin-deprecation": "2.0.0",
"eslint-plugin-mdx": "3.1.5",
Expand Down
1,991 changes: 1,205 additions & 786 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/background/messaging/messaging-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {

import { popup } from '@background/popup';

import { trackRpcRequestError } from './rpc-message-handler';
import { trackRpcRequestError } from './rpc-helpers';

export function getTabIdFromPort(port: chrome.runtime.Port) {
return port.sender?.tab?.id ?? 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { RpcErrorCode, type RpcMethodNames, createRpcErrorResponse } from '@leather.io/rpc';
import {
RpcErrorCode,
type RpcMethodNames,
type RpcRequests,
createRpcErrorResponse,
} from '@leather.io/rpc';

import { RouteUrls } from '@shared/route-urls';
import { RpcErrorMessage } from '@shared/rpc/methods/validation.utils';

import { queueAnalyticsRequest } from '@background/background-analytics';

import {
RequestParams,
listenForPopupClose,
makeSearchParamsWithDefaults,
triggerRequestWindowOpen,
} from './messaging-utils';
import { trackRpcRequestSuccess } from './rpc-message-handler';

interface HandleRpcMessageArgs {
method: RpcMethodNames;
Expand Down Expand Up @@ -42,3 +48,18 @@ export async function handleRpcMessage({
}),
});
}

interface TrackRpcRequestSuccess {
endpoint: RpcRequests['method'];
}
export async function trackRpcRequestSuccess(args: TrackRpcRequestSuccess) {
return queueAnalyticsRequest('rpc_request_successful', { ...args });
}

interface TrackRpcRequestError {
endpoint: RpcRequests['method'];
error: string;
}
export async function trackRpcRequestError(args: TrackRpcRequestError) {
return queueAnalyticsRequest('rpc_request_error', { ...args });
}
213 changes: 72 additions & 141 deletions src/background/messaging/rpc-message-handler.ts
Original file line number Diff line number Diff line change
@@ -1,155 +1,86 @@
import {
type LeatherRpcMethodMap,
RpcErrorCode,
type RpcRequests,
createRpcErrorResponse,
getAddresses,
open,
openSwap,
sendTransfer,
signMessage,
signPsbt,
stxCallContract,
stxDeployContract,
stxGetAddresses,
stxSignMessage,
stxSignStructuredMessage,
stxSignTransaction,
stxTransferSip9Nft,
stxTransferSip10Ft,
stxTransferStx,
supportedMethods,
} from '@leather.io/rpc';

import { queueAnalyticsRequest } from '@background/background-analytics';
import { rpcSwap } from '@background/messaging/rpc-methods/open-swap';
import { rpcStxSignTransaction } from '@background/messaging/rpc-methods/stx-sign-transaction';

import { getTabIdFromPort, listenForOriginTabClose } from './messaging-utils';
import { rpcGetAddresses } from './rpc-methods/get-addresses';
import { rpcOpen } from './rpc-methods/open';
import { rpcSendTransfer } from './rpc-methods/send-transfer';
import { rpcSignMessage } from './rpc-methods/sign-message';
import { rpcSignPsbt } from './rpc-methods/sign-psbt';
import { getAddressesHandler, stxGetAddressesHandler } from './rpc-methods/get-addresses';
import { openHandler } from './rpc-methods/open';
import { openSwapHandler } from './rpc-methods/open-swap';
import { sendTransferHandler } from './rpc-methods/send-transfer';
import { signMessageHandler } from './rpc-methods/sign-message';
import { signPsbtHandler } from './rpc-methods/sign-psbt';
import {
rpcSignStacksMessage,
rpcSignStacksStructuredMessage,
stxSignMessageHandler,
stxSignStructuredMessageHandler,
} from './rpc-methods/sign-stacks-message';
import { rpcStxCallContract } from './rpc-methods/stx-call-contract';
import { rpcStxDeployContract } from './rpc-methods/stx-deploy-contract';
import { rpcStxGetAddresses } from './rpc-methods/stx-get-addresses';
import { rpcStxTransferSip9Nft } from './rpc-methods/stx-transfer-sip9-nft';
import { rpcStxTransferSip10Ft } from './rpc-methods/stx-transfer-sip10-ft';
import { rpcStxTransferStx } from './rpc-methods/stx-transfer-stx';
import { rpcSupportedMethods } from './rpc-methods/supported-methods';

export async function rpcMessageHandler(message: RpcRequests, port: chrome.runtime.Port) {
listenForOriginTabClose({ tabId: port.sender?.tab?.id });

switch (message.method) {
case open.method: {
await rpcOpen(message, port);
break;
}
case openSwap.method: {
await rpcSwap(message, port);
break;
}
case getAddresses.method: {
await rpcGetAddresses(message, port);
break;
}

case signMessage.method: {
await rpcSignMessage(message, port);
break;
}

case sendTransfer.method: {
await rpcSendTransfer(message, port);
break;
}

case signPsbt.method: {
await rpcSignPsbt(message, port);
break;
}

case stxCallContract.method: {
await rpcStxCallContract(message, port);
break;
}

case stxDeployContract.method: {
await rpcStxDeployContract(message, port);
break;
}

case stxSignTransaction.method: {
await rpcStxSignTransaction(message, port);
break;
}

case supportedMethods.method: {
rpcSupportedMethods(message, port);
break;
}

case stxSignMessage.method: {
await rpcSignStacksMessage(message, port);
break;
}

case stxSignStructuredMessage.method: {
await rpcSignStacksStructuredMessage(message, port);
break;
}

case stxGetAddresses.method: {
await rpcStxGetAddresses(message, port);
break;
}

case stxTransferStx.method: {
await rpcStxTransferStx(message, port);
break;
}

case stxTransferSip10Ft.method: {
await rpcStxTransferSip10Ft(message, port);
break;
}

case stxTransferSip9Nft.method: {
await rpcStxTransferSip9Nft(message, port);
break;
}

default:
chrome.tabs.sendMessage(
getTabIdFromPort(port),
createRpcErrorResponse(message.method, {
id: message.id,
error: {
code: RpcErrorCode.METHOD_NOT_FOUND,
message: `"${message.method}" is not supported. Try running \`.request('supportedMethods')\` to see what Leather can do, or check out our developer documentation at https://leather.gitbook.io/developers/home/welcome`,
},
})
);
break;
}
import { stxCallContractHandler } from './rpc-methods/stx-call-contract';
import { stxDeployContractHandler } from './rpc-methods/stx-deploy-contract';
import { stxSignTransactionHandler } from './rpc-methods/stx-sign-transaction';
import { stxTransferSip9NftHandler } from './rpc-methods/stx-transfer-sip9-nft';
import { stxTransferSip10FtHandler } from './rpc-methods/stx-transfer-sip10-ft';
import { stxTransferStxHandler } from './rpc-methods/stx-transfer-stx';
import { supportedMethodsHandler } from './rpc-methods/supported-methods';

type RpcHandler<T> = (request: T, port: chrome.runtime.Port) => Promise<void> | void;

type RpcHandlers = {
[Method in keyof LeatherRpcMethodMap]: RpcHandler<LeatherRpcMethodMap[Method]['request']>;
};

const rpcHandlers: Partial<RpcHandlers> = {};

function registerRpcRequestHandler<M extends RpcRequests['method']>(
method: M,
handler: RpcHandler<LeatherRpcMethodMap[M]['request']>
) {
rpcHandlers[method] = handler;
}

interface TrackRpcRequestSuccess {
endpoint: RpcRequests['method'];
}
export async function trackRpcRequestSuccess(args: TrackRpcRequestSuccess) {
return queueAnalyticsRequest('rpc_request_successful', { ...args });
export function defineRpcRequestHandler<M extends RpcRequests['method']>(
method: M,
handler: RpcHandler<LeatherRpcMethodMap[M]['request']>
) {
return [method, handler] as const;
}

interface TrackRpcRequestError {
endpoint: RpcRequests['method'];
error: string;
}
export async function trackRpcRequestError(args: TrackRpcRequestError) {
return queueAnalyticsRequest('rpc_request_error', { ...args });
export async function rpcMessageHandler(request: RpcRequests, port: chrome.runtime.Port) {
listenForOriginTabClose({ tabId: port.sender?.tab?.id });

// This typecast safely bypasses the compiler since it cannot infer or narrow
// the type to know the `request` being passed to `handler` is the correct
// one. Type safety is guaranteed by `registerRpcRequestHandler`
const handler = rpcHandlers[request.method] as RpcHandler<any>;

if (handler) return await handler(request, port);

chrome.tabs.sendMessage(
getTabIdFromPort(port),
createRpcErrorResponse(request.method, {
id: request.id,
error: {
code: RpcErrorCode.METHOD_NOT_FOUND,
message: `"${request.method}" is not supported. Try running \`.request('supportedMethods')\` to see what Leather can do, or check out our developer documentation at https://leather.gitbook.io/developers/home/welcome`,
},
})
);
}

registerRpcRequestHandler(...getAddressesHandler);
registerRpcRequestHandler(...openHandler);
registerRpcRequestHandler(...openSwapHandler);
registerRpcRequestHandler(...sendTransferHandler);
registerRpcRequestHandler(...signMessageHandler);
registerRpcRequestHandler(...signPsbtHandler);
registerRpcRequestHandler(...stxCallContractHandler);
registerRpcRequestHandler(...stxDeployContractHandler);
registerRpcRequestHandler(...stxGetAddressesHandler);
registerRpcRequestHandler(...stxSignMessageHandler);
registerRpcRequestHandler(...stxSignStructuredMessageHandler);
registerRpcRequestHandler(...stxSignTransactionHandler);
registerRpcRequestHandler(...stxTransferSip10FtHandler);
registerRpcRequestHandler(...stxTransferSip9NftHandler);
registerRpcRequestHandler(...stxTransferStxHandler);
registerRpcRequestHandler(...supportedMethodsHandler);
15 changes: 12 additions & 3 deletions src/background/messaging/rpc-methods/get-addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
makeSearchParamsWithDefaults,
triggerRequestWindowOpen,
} from '../messaging-utils';
import { trackRpcRequestSuccess } from '../rpc-message-handler';
import { trackRpcRequestSuccess } from '../rpc-helpers';
import { defineRpcRequestHandler } from '../rpc-message-handler';

export function makeRpcAddressesMessageListener(eventName: 'getAddresses' | 'stx_getAddresses') {
function makeRpcAddressesMessageListener(eventName: 'getAddresses' | 'stx_getAddresses') {
return async (
message: RpcRequest<typeof getAddresses> | RpcRequest<typeof stxGetAddresses>,
port: chrome.runtime.Port
Expand Down Expand Up @@ -47,4 +48,12 @@ export function makeRpcAddressesMessageListener(eventName: 'getAddresses' | 'stx
};
}

export const rpcGetAddresses = makeRpcAddressesMessageListener('getAddresses');
export const getAddressesHandler = defineRpcRequestHandler(
getAddresses.method,
makeRpcAddressesMessageListener(getAddresses.method)
);

export const stxGetAddressesHandler = defineRpcRequestHandler(
stxGetAddresses.method,
makeRpcAddressesMessageListener(stxGetAddresses.method)
);
9 changes: 5 additions & 4 deletions src/background/messaging/rpc-methods/open-swap.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { type RpcRequest, createRpcSuccessResponse, type openSwap } from '@leather.io/rpc';
import { createRpcSuccessResponse, openSwap } from '@leather.io/rpc';

import { RouteUrls } from '@shared/route-urls';
import { replaceRouteParams } from '@shared/utils/replace-route-params';

import { makeSearchParamsWithDefaults, triggerSwapWindowOpen } from '../messaging-utils';
import { trackRpcRequestSuccess } from '../rpc-message-handler';
import { trackRpcRequestSuccess } from '../rpc-helpers';
import { defineRpcRequestHandler } from '../rpc-message-handler';

export async function rpcSwap(message: RpcRequest<typeof openSwap>, port: chrome.runtime.Port) {
export const openSwapHandler = defineRpcRequestHandler(openSwap.method, async (message, port) => {
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [['requestId', message.id]]);
const { base = 'STX', quote } = message?.params || {};

Expand Down Expand Up @@ -37,4 +38,4 @@ export async function rpcSwap(message: RpcRequest<typeof openSwap>, port: chrome
result: { message: 'Success' },
})
);
}
});
9 changes: 5 additions & 4 deletions src/background/messaging/rpc-methods/open.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { type RpcRequest, createRpcSuccessResponse, type open } from '@leather.io/rpc';
import { createRpcSuccessResponse, open } from '@leather.io/rpc';

import { RouteUrls } from '@shared/route-urls';

import { makeSearchParamsWithDefaults, triggerRequestWindowOpen } from '../messaging-utils';
import { trackRpcRequestSuccess } from '../rpc-message-handler';
import { trackRpcRequestSuccess } from '../rpc-helpers';
import { defineRpcRequestHandler } from '../rpc-message-handler';

export async function rpcOpen(message: RpcRequest<typeof open>, port: chrome.runtime.Port) {
export const openHandler = defineRpcRequestHandler(open.method, async (message, port) => {
const { urlParams, tabId } = makeSearchParamsWithDefaults(port, [['requestId', message.id]]);

await triggerRequestWindowOpen(RouteUrls.Home, urlParams);
Expand All @@ -18,4 +19,4 @@ export async function rpcOpen(message: RpcRequest<typeof open>, port: chrome.run
result: { message: 'Success' },
})
);
}
});
Loading

0 comments on commit 8e4169a

Please sign in to comment.