Implements tRPC-based communication between VS Code extension host and React webviews. Use when creating new webview procedures (queries, mutations, subscriptions), adding a new webview router, wiring up a webview controller, using the tRPC client from React components, applying telemetry middleware (telemetryMiddlewareBody), or supporting AbortSignal-based cancellation in webview operations.
Implements tRPC-based communication between VS Code extension host and React webviews. Use when creating new webview procedures (queries, mutations, subscriptions), adding a new webview router, wiring up a webview controller, using the tRPC client from React components, applying telemetry middleware (telemetryMiddlewareBody), or supporting AbortSignal-based cancellation in webview operations.
Webview tRPC Messaging
Type-safe RPC communication between the VS Code extension host (server) and React webviews (client) using tRPC.
Construction-only panels are opened with a factory function that builds the
config + router context and calls the openAppWebview preset (which pre-fills
the app router, caller factory, and bundle layout):
The returned AppWebviewController handle exposes panel, onDisposed,
revealToForeground, isDisposed, and dispose. Genuinely stateful panels may
still extend WebviewController from @microsoft/vscode-ext-webview/host
directly instead of using the factory.
Important: The webviewName field passed to openAppWebview is the
registry key (viewType, must match a key in WebviewRegistry, e.g.
collectionView). The webviewName in the tRPC context is a telemetry
label used in telemetry event names. These may be the same string but serve
different purposes -- do not confuse them.
5. Register in WebviewRegistry
Add your React component to the registry. The key must match the webviewName
passed to openAppWebview (viewType). The WebviewName type (exported from
the same file) ensures compile-time validation of webview names.
Telemetry: publicProcedure vs publicProcedureWithTelemetry
Base
When to use
ctx.actionContext
publicProcedure
Fire-and-forget, no external calls, telemetry reported separately
absent (do not read it)
publicProcedureWithTelemetry
Default choice. Any procedure touching DB, network, or user-visible work
Guaranteed, injected by the DocumentDB TelemetryRunner
publicProcedureWithTelemetry is publicProcedure.use(telemetryMiddlewareBody(documentDbTelemetryRunner, ...)) (built in trpc.ts). The framework's telemetryMiddlewareBody delegates to the DocumentDB TelemetryRunner, which wraps the call in callWithTelemetryAndErrorHandling, contributes the full IActionContext to ctx.actionContext, auto-generates a telemetry event named documentDB.rpc.{type}.{path}, and records errors, duration, and abort status.
actionContext is not a field on the base RouterContext — it is an additive enrichment. Narrow to WithTelemetry<RouterContext> (= RouterContext & { actionContext }) in an instrumented procedure to read it; a plain publicProcedure procedure narrows to bare RouterContext, so reading actionContext there is a compile error instead of a runtime undefined.
When publicProcedureWithTelemetry detects an aborted signal, the DocumentDB TelemetryRunner sets telemetry.properties.aborted = 'true' and result = 'Canceled' automatically.
Subscriptions
Subscriptions stream multiple values from server to client using async generators:
// Server (router)streamData: publicProcedureWithTelemetry
.input(z.object({ batchSize: z.number() }))
.subscription(asyncfunction* ({ input, ctx }) {
const myCtx = ctx asRouterContext;
for (let i = 0; i < total; i += input.batchSize) {
if (myCtx.signal?.aborted) return; // check before each yieldconst batch = awaitfetchBatch(i, input.batchSize);
yield batch;
}
}),
// Client (React)const sub = trpcClient.mongoClusters.myView.streamData.subscribe(
{ batchSize: 100 },
{
onData(batch) { /* handle each batch */ },
onComplete() { /* all done */ },
onError(err) { /* handle error */ },
},
);
// To stop:
sub.unsubscribe();
useConfiguration<T>() retrieves the initial config passed to WebviewController constructor (serialized via encodeURIComponent(JSON.stringify(...))).
Common Pitfalls
Never use any in procedure context casts — narrow with ctx as WithTelemetry<RouterContext> when the procedure reads telemetry (ctx.actionContext.telemetry), or ctx as RouterContext otherwise
Always prefer publicProcedureWithTelemetry unless you have a specific reason not to
Always check myCtx.signal?.aborted in long-running loops — not checking causes wasted work after client cancels
Do not mutate the shared context object — WebviewController clones it per-operation already, but router code should treat ctx as read-only
Input validation uses zod — always define .input(z.object({...})) for type safety
The commonRouter handles cross-cutting concerns (error reporting, telemetry events, surveys, URL opening) — do not duplicate these in view-specific routers