Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ grpc-web-devtools now also supports [connect-web](https://github.com/bufbuild/co

```ts
// __CONNECT_WEB_DEVTOOLS__ is loaded in as a script, so it is not guaranteed to be loaded before your code.
const interceptors: Interceptor[] = window.__CONNECT_WEB_DEVTOOLS__ !== "undefined" ?
const interceptors: Interceptor[] = window.__CONNECT_WEB_DEVTOOLS__ !== undefined ?
[window.__CONNECT_WEB_DEVTOOLS__]
: [];
// To get around the fact that __CONNECT_WEB_DEVTOOLS__ might not be loaded, we can listen for a custom event,
// and then push the interceptor to our array once loaded.
window.addEventListener("connect-web-dev-tools-ready", () => {
if (typeof window.__CONNECT_WEB_DEVTOOLS__ !== "undefined") {
if (window.__CONNECT_WEB_DEVTOOLS__ !== undefined) {
interceptors.push(window.__CONNECT_WEB_DEVTOOLS__);
}
});
Expand Down
147 changes: 102 additions & 45 deletions public/connect-web-interceptor.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,110 @@
const MethodKind = {
Unary: 0,
ServerStreaming: 1,
};

// This interceptor will be passed every request and response. We will take that request and response
// and post a message to the window. This will allow us to access this message in the content script. This
// is all to make the manifest v3 happy.
const interceptor =
(next) =>
async (req) => {
return await next(req).then((resp) => {
if (!resp.stream) {
window.postMessage({
type: "__GRPCWEB_DEVTOOLS__",
methodType: "unary",
method: req.method.name,
request: req.message.toJson(),
response: resp.message.toJson(),
}, "*")
return resp;
} else {
return {
...resp,
async read() {
const streamResp = await resp.read();
// "value" could be undefined when "done" is true
if (streamResp.value) {
window.postMessage({
type: "__GRPCWEB_DEVTOOLS__",
methodType: "server_streaming",
method: req.method.name,
request: req.message.toJson(),
response: streamResp.value.toJson(),
}, "*");
}
return streamResp;
}
}
}
}).catch((e) => {
window.postMessage({
type: "__GRPCWEB_DEVTOOLS__",
methodType: req.stream ? "server_streaming" : "unary",
method: req.method.name,
request: req.message.toJson(),
response: undefined,
error: {
message: e.message,
code: e.code,
}
}, "*")
const interceptor = (next) => async (req) => {
switch (req.method.kind) {
case MethodKind.Unary:
try {
assert(!req.stream);
const resp = await next(req);
assert(!resp.stream);
post(req.service, req.method, req.message, resp.message);
return resp;
} catch (e) {
post(req.service, req.method, req.message, undefined, e);
throw e;
}
case MethodKind.ServerStreaming:
try {
assert(req.stream);
let reqMessage;
const resp = await next({
...req,
message: wrap(
req.message, message => {
reqMessage = message;
},
reason => {
post(req.service, req.method, reqMessage, undefined, reason);
})
});
assert(resp.stream);
return {
...resp,
message: wrap(
resp.message,
message => {
post(req.service, req.method, reqMessage, message);
}, reason => {
post(req.service, req.method, reqMessage, undefined, reason);
})
};
} catch (e) {
post(req.service, req.method, undefined, undefined, e);
throw e;
});
}
default:
assert(false);
}
};

function post(service, method, requestMessage, responseMessage, error = undefined) {
const m = {
type: "__GRPCWEB_DEVTOOLS__",
methodType: method.kind === MethodKind.Unary ? "unary" : "server_streaming",
method: service.typeName + "/" + method.name,
request: undefined,
response: undefined,
};
if (requestMessage) {
try {
m.request = requestMessage.toJson({emitDefaultValues: true});
} catch (e) {
// serializing google.protobuf.Any requires a type registry
}
}
if (responseMessage) {
try {
m.response = responseMessage.toJson({emitDefaultValues: true});
} catch (e) {
// serializing google.protobuf.Any requires a type registry
}
}
if (error) {
m.error = {
message: error.message,
code: error.code,
};
}
window.postMessage(m, "*");
}

function wrap(it, onMessage, onError) {
async function* generator() {
try {
for await (const m of it) {
onMessage(m);
yield m;
}
} catch (e) {
onError(e);
throw e;
}
}

return generator();
}

function assert(condition) {
if (!condition) {
throw new Error("assertion failed");
}
}

window.__CONNECT_WEB_DEVTOOLS__ = interceptor;

Expand Down