penumbra-zone / web

Apache License 2.0
12 stars 15 forks source link

simplify third-party consumption of services #341

Open turbocrime opened 9 months ago

turbocrime commented 9 months ago

Goal: ConnectRPC/Protobuf ServiceImpls should be third-party hostable

my notes on this goal. read this as a conceptual sketch - suggestions, counterpoints, and disagreements welcome

brief

in the interest of an open ecosystem, we should make our service implementations directly useful to developers of complementary and alternative tools and wallets. this requires designing our implementations in such a way that they can operate as a 'drop-in' component within diverse environments.

current

our implementations are tightly coupled to our browser extension - they directly message and control lifecycle of extension documents, directly access extension sessionStorage and localStorage, and directly query indexedDb.

some functionality and types are imported from other packages, such as our types package, greatly obfuscating the dependency graph.

breakdown

  1. isomorphic code targeting standard browser DOM first, node second
    • never call chrome.runtime
  2. minimize package deps
    • depend on: buf registry and connectrpc
    • avoid depending on: our other packages, general npm packages
  3. eliminate indexeddb queries, extension storage queries
    • abstract all details of our storage formats and interfaces
    • ok to query pb-specified services
    • use contextValues to inject data not present in request
  4. eliminate most imported types
    • buf registry should provide most types
    • convenience types should be defined locally and not exported
  5. eliminate most imported code
    • provide a minimal util or convenience module per service, for functionality duplicated between methods
    • provide type guards/getters per service, to cover specific pb-spec shortcomings

approach

HandlerContext and contextValues

much of this can be achieved by using HandlerContext to access contextValues, instead of directly interacting with other parts of the extension. this makes awareness of the runtime the responsibility of the runtime adapter.

in routing-refactor, contextValues were used to simply inject broad storage access, to maintain existing use. now that routing-refactor has landed, it would be a good first step to begin abstracting storage access into context values.

it may be possible to use 'interceptors' configured by the adapter to inject things like interactive approval.

offscreen.html

in a chrome extension, offscreen.html is the appropriate location to run code targeting the standard browser dom.

this may seem like a drastic choice, but we are already entering services via the chrome runtime, which is the way into offscreen, so this would involve minimal change. either the connection manager or the adapter would become the new boundary. service implementations themselves would continue to operate identically, except for offscreenClient, which would no longer be necessary - handlers could just call wasm directly as needed.

offscreenClient would be removed, and the only offscreen lifecycle management necessary would become launch upon first client connection, and terminate upon last client close.

potential difficulty: access to @penumbra-zone/services functionality - but this should be minimized and abstracted.

when is the host responsible?

development of things like interactive transaction approval has been a complicated point for service impls, involving specialized tools located within the router package that manage things like document lifecycle and intra-extension messaging. impl awareness of extension architecture and message formats is a failure: it means we have tightly coupled the impl to our extension.

if the service implemenation needs some specific functionality from the host such as interactive approval, define a conceptually simple interface - hopefully a static object or Promise - that can be injected via context. the host is responsible for abstracting its design to meet that interface.

grod220 commented 9 months ago

Some initial thoughts:

turbocrime commented 9 months ago

module reorganization

the transport, router, and services are not correctly delimited

turbocrime commented 9 months ago

channelTransport simplification

current in-page transport dynamically creates an internal router, with a generated method per rpc endpoint. this can be simplified to a single stream and single unary transport method meeting the appropriate interface