-
Notifications
You must be signed in to change notification settings - Fork 4
Add protobuf-ts interceptor support and implementation plan for #101 #603
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
base: codex-protobuf-ts
Are you sure you want to change the base?
Changes from all commits
ece4058
78ba5ce
8fd90e0
535b9b3
38fac23
cb2e142
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,18 @@ | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| test("__GRPCWEB_DEVTOOLS__", () => { | ||
| test("__GRPCWEB_DEVTOOLS__ grpc-web internals include interceptor arrays", () => { | ||
| const grpcWebIndexPath = path.resolve(__dirname, "..", "node_modules", "grpc-web", "index.js"); | ||
| const grpcWebIndexContent = fs.readFileSync(grpcWebIndexPath, "utf-8"); | ||
|
|
||
| expect(grpcWebIndexContent).toContain("this.b=[];this.h=[];this.g=[];this.f=[];this.c=[]"); | ||
| // grpc-web internals are minified and changed between versions. | ||
| // Accept both older (h/g/b) and newer (h/m) interceptor field layouts. | ||
| const hasLegacyLayout = | ||
| grpcWebIndexContent.includes("this.h=[]") && | ||
| grpcWebIndexContent.includes("this.g=[]") && | ||
| grpcWebIndexContent.includes("this.b=[]"); | ||
| const hasModernLayout = | ||
| grpcWebIndexContent.includes("this.h=[]") && grpcWebIndexContent.includes("this.m=[]"); | ||
|
|
||
| expect(hasLegacyLayout || hasModernLayout).toBe(true); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| test("protobuf-ts interceptor is exposed in injected globals", () => { | ||
| const indexPath = path.resolve(__dirname, "..", "content-scripts", "src", "main", "index.ts"); | ||
| const content = fs.readFileSync(indexPath, "utf-8"); | ||
|
|
||
| expect(content).toContain("protobufTsInterceptor"); | ||
| expect(content).toContain("value: protobufTsInterceptor"); | ||
| }); | ||
|
|
||
| test("protobuf-ts interceptor handles unary and server streaming flows", () => { | ||
| const filePath = path.resolve( | ||
| __dirname, | ||
| "..", | ||
| "content-scripts", | ||
| "src", | ||
| "main", | ||
| "protobuf-ts.ts", | ||
| ); | ||
| const content = fs.readFileSync(filePath, "utf-8"); | ||
|
|
||
| expect(content).toContain("interceptUnary"); | ||
| expect(content).toContain("interceptServerStreaming"); | ||
| expect(content).toContain('responseMessage: "EOF"'); | ||
| expect(content).toContain("toMetadataRecord(call.requestHeaders)"); | ||
| expect(content).toContain("mergeMetadata"); | ||
| expect(content).toContain("void call.headers"); | ||
| expect(content).toContain("void call.trailers"); | ||
| expect(content).toContain("responseMetadata = mergeMetadata(headers, responseMetadata)"); | ||
| expect(content).toContain("responseMetadata = mergeMetadata(responseMetadata, trailers)"); | ||
| }); | ||
|
|
||
| test("protobuf-ts interceptor avoids creating unhandled rejections", () => { | ||
| const filePath = path.resolve( | ||
| __dirname, | ||
| "..", | ||
| "content-scripts", | ||
| "src", | ||
| "main", | ||
| "protobuf-ts.ts", | ||
| ); | ||
| const content = fs.readFileSync(filePath, "utf-8"); | ||
|
|
||
| expect(content).toContain("void call"); | ||
| expect(content).toContain(".catch((error: RpcError) => {"); | ||
| expect(content).not.toContain("throw error"); | ||
| }); | ||
|
Comment on lines
+1
to
+32
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |||||
| }, | ||||||
| "devDependencies": { | ||||||
| "@connectrpc/connect": "2.1.0", | ||||||
| "@protobuf-ts/runtime-rpc": "^2.11.1", | ||||||
|
||||||
| "@protobuf-ts/runtime-rpc": "^2.11.1", | |
| "@protobuf-ts/runtime-rpc": "2.11.1", |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,156 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NextServerStreamingFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NextUnaryFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RpcError, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RpcInterceptor, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RpcMetadata, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ServerStreamingCall, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UnaryCall, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@protobuf-ts/runtime-rpc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { postMessageToContentScript } from "./post-message-to-content-script"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const toMetadataRecord = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| metadata: undefined | Readonly<RpcMetadata>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Record<string, string> | undefined => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (metadata === undefined) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Object.entries(metadata).reduce<Record<string, string>>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| acc, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| acc[key] = Array.isArray(value) ? value.map(String).join(", ") : String(value); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return acc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+31
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mergeMetadata = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...metadataItems: Array<undefined | Readonly<RpcMetadata>> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Record<string, string> | undefined => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const records = metadataItems | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map((metadata) => toMetadataRecord(metadata)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((metadata): metadata is Record<string, string> => metadata !== undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (records.length === 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Object.assign({}, ...records); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const toSerializableError = (error: RpcError) => ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: error.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: error.code, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: error.message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stack: error.stack, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const protobufTsInterceptor: RpcInterceptor = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interceptUnary(next: NextUnaryFn, method, input, options): UnaryCall { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const id = Math.random().toString(36).slice(2, 6); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const call = next(method, input, options); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| methodName: method.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serviceName: method.service.typeName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestMessage: call.request, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestMetadata: toMetadataRecord(call.requestHeaders), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+66
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void call | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((finishedUnaryCall) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMetadata: mergeMetadata(finishedUnaryCall.headers, finishedUnaryCall.trailers), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMessage: finishedUnaryCall.response, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMessage: "EOF", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch((error: RpcError) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMessage: toSerializableError(error), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorMetadata: toMetadataRecord(error.meta), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return call; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interceptServerStreaming( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| next: NextServerStreamingFn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): ServerStreamingCall { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const id = Math.random().toString(36).slice(2, 6); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const call = next(method, input, options); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let responseMetadata: Record<string, string> | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void call.headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((headers) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMetadata = mergeMetadata(headers, responseMetadata); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMetadata, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch(() => void 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void call.trailers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .then((trailers) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMetadata = mergeMetadata(responseMetadata, trailers); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| postMessageToContentScript({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| responseMetadata, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .catch(() => void 0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+101
to
+121
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void Promise.allSettled([ | |
| call.headers, | |
| call.trailers, | |
| ]).then( | |
| ([ | |
| headersResult, | |
| trailersResult, | |
| ]) => { | |
| responseMetadata = mergeMetadata( | |
| headersResult.status === "fulfilled" ? headersResult.value : undefined, | |
| trailersResult.status === "fulfilled" ? trailersResult.value : undefined, | |
| ); | |
| }, | |
| ); | |
| let responseHeaders: RpcMetadata | undefined; | |
| // Populate metadata as soon as headers are available, then merge in trailers later. | |
| void call.headers | |
| .then((headers) => { | |
| responseHeaders = headers; | |
| responseMetadata = mergeMetadata(headers, undefined); | |
| }) | |
| .catch(() => { | |
| // Ignore header errors here; they will surface via onError. | |
| }); | |
| void call.trailers | |
| .then((trailers) => { | |
| responseMetadata = mergeMetadata(responseHeaders, trailers); | |
| }) | |
| .catch(() => { | |
| // Ignore trailer errors here; they will surface via onError. | |
| }); |
Copilot
AI
Mar 12, 2026
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.
Streamed responses are posted with responseMessage: message without any normalization. If protobuf-ts messages include internal fields (e.g. $typeName, $unknown) like other protobuf runtimes, these will show up in Devtools and diverge from the sanitized shapes used by the Connect-ES interceptor. Consider applying the same message sanitization/normalization here before posting.
Copilot
AI
Mar 10, 2026
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.
The onError callback in protobuf-ts's RpcOutputStream is typed as (error: Error) => void, but here error is passed to toSerializableError (which expects RpcError) and error.meta is accessed (which doesn't exist on Error). At runtime, the actual errors in gRPC calls are indeed RpcError instances so this works in practice, but the type mismatch means TypeScript won't catch misuses. The error parameter should be cast to RpcError (e.g., as RpcError) to make the type intent explicit and ensure error.code and error.meta accesses are type-safe.
| postMessageToContentScript({ | |
| id, | |
| responseMessage: toSerializableError(error), | |
| errorMetadata: toMetadataRecord(error.meta), | |
| const rpcError = error as RpcError; | |
| postMessageToContentScript({ | |
| id, | |
| responseMessage: toSerializableError(rpcError), | |
| errorMetadata: toMetadataRecord(rpcError.meta), |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # Issue #101 Plan: Integrate protobuf-ts support | ||
|
|
||
| ## Goal | ||
|
|
||
| Add first-class support for applications using `protobuf-ts` with gRPC-Web transport so requests and responses are captured in gRPC Devtools without custom user code. | ||
|
|
||
| ## Delivery plan | ||
|
|
||
| 1. **Expose a built-in protobuf-ts interceptor in injected globals** | ||
| - Add `window.__gRPC_devtools__.protobufTsInterceptor` to the main injected entrypoint. | ||
| - Keep existing `gRPC-Web` and `Connect-ES` support unchanged. | ||
|
|
||
| 2. **Implement protobuf-ts interceptor behavior** | ||
| - Add unary interception: | ||
| - send request metadata and request message; | ||
| - send response metadata + response message; | ||
| - send EOF marker; | ||
| - capture and forward error metadata/message details. | ||
| - Add server-streaming interception: | ||
| - send initial request metadata and message; | ||
| - forward each streamed message; | ||
| - send EOF on completion; | ||
| - send structured error payload on stream errors. | ||
|
|
||
| 3. **Normalize protobuf-ts metadata for Devtools transport format** | ||
| - Convert `Record<string, string | string[]>` metadata to `Record<string, string>` for existing message pipeline compatibility. | ||
|
|
||
| 4. **Document public usage in README** | ||
| - Add a `protobuf-ts` section with a `grpc-devtools.ts` snippet and transport wiring example for `@protobuf-ts/grpcweb-transport`. | ||
|
|
||
| 5. **Add a dedicated protobuf-ts example** | ||
| - Add a self-contained example directory under `example/protobuf-ts` that demonstrates interceptor wiring with `GrpcWebFetchTransport`. | ||
|
|
||
| 6. **Validation** | ||
| - Build content scripts to ensure TypeScript/webpack compilation succeeds. | ||
| - Build the full extension workspace to verify no regressions across packages. | ||
|
|
||
| ## Non-goals (for this PR) | ||
|
|
||
| - Auto-detection/injection into user transports without adding the interceptor. | ||
| - New UI behavior in Devtools panel. | ||
| - Support beyond unary and server-streaming APIs used in gRPC-Web transport. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # protobuf-ts Example | ||
|
|
||
| This example shows how to wire `grpc-devtools` with [`protobuf-ts`](https://github.com/timostamm/protobuf-ts) and the gRPC-Web transport. | ||
|
|
||
| ## grpc-devtools.ts | ||
|
|
||
| ```ts | ||
| import type { RpcInterceptor } from "@protobuf-ts/runtime-rpc"; | ||
|
|
||
| declare const __gRPC_devtools__: | ||
| | undefined | ||
| | { | ||
| protobufTsInterceptor: RpcInterceptor; | ||
| }; | ||
|
|
||
| export const interceptors: RpcInterceptor[] = | ||
| typeof __gRPC_devtools__ === "object" | ||
| ? [ | ||
| __gRPC_devtools__.protobufTsInterceptor, | ||
| ] | ||
| : []; | ||
| ``` | ||
|
|
||
| ## transport.ts | ||
|
|
||
| ```ts | ||
| import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport"; | ||
| import { interceptors } from "./grpc-devtools"; | ||
|
|
||
| export const transport = new GrpcWebFetchTransport({ | ||
| baseUrl: "http://localhost:3003", | ||
| interceptors, | ||
| }); | ||
| ``` | ||
|
|
||
| ## Included source snippets | ||
|
|
||
| - `client/src/grpc-devtools.ts` | ||
| - `client/src/transport.ts` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import type { RpcInterceptor } from "@protobuf-ts/runtime-rpc"; | ||
|
|
||
| declare const __gRPC_devtools__: | ||
| | undefined | ||
| | { | ||
| protobufTsInterceptor: RpcInterceptor; | ||
| }; | ||
|
|
||
| export const interceptors: RpcInterceptor[] = | ||
| typeof __gRPC_devtools__ === "object" | ||
| ? [ | ||
| __gRPC_devtools__.protobufTsInterceptor, | ||
| ] | ||
| : []; |
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.
The protobuf-ts snippet uses
baseUrl: "http://localhost:8080", but the new protobuf-ts example (and existing Connect-ES docs) usehttp://localhost:3003for the backend. This mismatch can cause copy/paste setups to fail; consider aligning the README snippet with the example/baseUrl used elsewhere.