-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request/Response Middleware #119
Open
esterlus
wants to merge
3
commits into
MetaMask:main
Choose a base branch
from
hoprnet:rpch/json-rpc-provider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
--- | ||
sip: 17 | ||
title: Request/Response Middleware | ||
status: Draft | ||
author: Ronny Esterluss <[email protected]> (@esterlus), Tino Breddin <[email protected]> (@tolbrino) | ||
created: 2023-11-15 | ||
--- | ||
|
||
## Abstract | ||
|
||
Introduce a new endowment that would allow a snap to register itself as a request middleware. | ||
|
||
## Motivation | ||
|
||
We want to build a snap that gives users full privacy when doing JSON RPC calls using Metamask. | ||
This snap will leverage [RPCh](https://rpch.net/) to relay requests and | ||
improve the IP-privacy of all RPC calls. | ||
The original JSON RPC provider, which is configured in Metamask for the active | ||
network, will still be used as the target of the request. However the request | ||
will be relayed through [HOPRNet](https://network.hoprnet.org/) first before | ||
finally sent to the RPC provider. | ||
|
||
## Specification | ||
|
||
> Formal specifications are written in Typescript. | ||
|
||
### Language | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | ||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and | ||
"OPTIONAL" written in uppercase in this document are to be interpreted as | ||
described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) | ||
|
||
### Snaps permissions | ||
|
||
This SIP specifies a permission named `endowment:request-middleware`. | ||
The permission signals to the platform that the snap wants to act as | ||
request middleware and modify the original requests headers, body and/or target. | ||
|
||
This permission is specified as follows in `snap.manifest.json` files: | ||
|
||
```json | ||
{ | ||
"initialPermissions": { | ||
"endowment:request-middleware": {} | ||
} | ||
} | ||
``` | ||
|
||
The order in which the middleware will be called is not guaranteed and may vary. | ||
|
||
3 special types of middlewares are supported: | ||
|
||
1. Middleware entry | ||
Is guaranteed to be called first in the middleware chain. Only a single entry | ||
can be enabled. | ||
2. Middleware exit | ||
Is guaranteed to be called last in the middleware chain. Only a single exit | ||
can be enabled. | ||
3. Execution point | ||
Is guaranteed to be called after the entire middleware chain. Only a single | ||
execution point can be enabled. This middleware will execute the request | ||
directly and return the response. | ||
|
||
The special middleware types can be enabled through permission parameters which | ||
are set to `false` by default: | ||
|
||
```json | ||
{ | ||
"initialPermissions": { | ||
"endowment:request-middleware": { | ||
"isEntry": false, | ||
"isExit": false, | ||
"isExecutionPoint": false | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Snaps exports | ||
|
||
This SIP specifies 2 new exported event handlers: `onRequest` and | ||
`onRequestTermination`. | ||
The correct event handler MUST be called whenever Metamask makes an outgoing | ||
JSON RPC request depending on the permission configuration of the middleware. | ||
|
||
#### Parameters | ||
|
||
An object containing the following fields: | ||
|
||
* `request`: the request containing headers and body data | ||
|
||
`headers`: This parameter MUST be present and be a flat object container key/value mappings. | ||
`body`: This parameter MUST be present and MUST adhere to the official [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification). | ||
|
||
* `provider`: the target JSON RPC provider url | ||
|
||
This parameter SHOULD be present. It contains the RPC URL of the intented RPC Provider (e.g. `https://mainnet.infura.io/v3/`). | ||
Metamask SHOULD use the configured RPC provider in the currently selected network. | ||
|
||
#### Returns | ||
|
||
`onRequest`: A promise resolving to the updated request object. | ||
`onRequestTermination`: A promise resolving to the response of the request or rejecting with an error. | ||
|
||
#### Examples | ||
|
||
```typescript | ||
import { OnRequestHandler, OnRequestTerminationHandler } from '@metamask/snaps-types'; | ||
import RPChSDK from '@rpch/sdk'; | ||
|
||
const sdk = new RPChSDK("<ClientId>"); | ||
|
||
export const onRequest: OnRequestHandler = async ({ | ||
request, | ||
provider, | ||
}) => { | ||
request.headers["CUSTOM-HEADER"] = "example value"; | ||
return request; | ||
}; | ||
|
||
export const onRequest: OnRequestTerminationHandler = async ({ | ||
request, | ||
provider, | ||
}) => { | ||
return sdk.send(request, { provider }); | ||
}; | ||
``` | ||
|
||
#### Type definitions | ||
|
||
```typescript | ||
|
||
type OnRequestHandler = | ||
(args: { request: Request, provider: string }) => Promise<Request>; | ||
|
||
type OnRequestTerminationHandler = | ||
(args: { request: Request, provider: string }) => Promise<Response>; | ||
|
||
type Request = { | ||
headers: object; | ||
body: JSONRPCRequest; | ||
}; | ||
|
||
type JSONRPCRequest = { | ||
readonly jsonrpc: '2.0'; | ||
id?: string | number | null; | ||
method: string; | ||
params?: any[] | object; | ||
}; | ||
|
||
type Response = { | ||
headers: object; | ||
body: JSONRPCResponse; | ||
status: number; | ||
}; | ||
|
||
type JSONRPCResponse = JSONRPCResult | JSONRPCError; | ||
|
||
type JSONRPCResult = { | ||
readonly jsonrpc: '2.0'; | ||
id?: string | number | null; | ||
result: any; | ||
}; | ||
|
||
type JSONRPCError = { | ||
readonly jsonrpc: '2.0'; | ||
id?: string | number | null; | ||
error: { | ||
code: number; | ||
message: string; | ||
data?: any; | ||
}; | ||
}; | ||
``` | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE). |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when there are multiple middleware snaps competing for these different points in the stack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I think this must be handled at 2 points: (1) Within MM where snaps are loaded which will simply skip over more middlewares for points which are filled already. (2) Allow the user to specify some order through the MM settings where its easy to show that these points are filled and limit that no more middlewares are enabled which would compete for such a point.
An alternative could be during installation of snaps which could be prevented if the middleware tries to fill a point which is already taken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, a middleware stack can run multiple middlewares in a row, and each one can run their own logic to modify the request/response, before it's passed on to the next middleware. Do you think we need to limit it to a single middleware?
I think middleware would be very difficult to explain to a user in a meaningful way. 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If its limited then I would move away from calling it a middleware and focus on specific use cases, similar to the original proposal in this SIP which focused on RPC http call execution.
I agree, the middleware concept is developer-oriented, nothing users are usually confronted with.