I’m primarily posting this to share a very obscure problem I ran into that was hard to workaround - and there might be a better workaround.
Context - I am developing an extension, which includes a server extension and JupyterLab frontend extension, which talks to endpoints on the server. I wanted to try and ensure the frontend and backend stay in sync.
I then tried using openapi-typescript and openapi-fetch to generate schema for this communication and to perform validation on requests.
openapi-fetch creates a typesafe client object which can perform requests and validation for you.
However, because of the way JupyterLab secures requests, in order for them to be received by the server, these need to be mediated via ServerConnection.makeRequest
which fills in the required XSRF etc. stuff.
This function has a similar API to fetch
and so I tried creating a wrapper JLfetch
function to pass requests to makeRequest
and then configured openapi-fetch
to call this instead of the regular fetch
.
The problem I encountered was that openapi-fetch
creates a Request
object prior to using the configured fetch function. Seemingly in Chrome, but not in Firefox (which is what I primarily use for prototyping), when one creates a Request
option for a POST request with a body, it converts the request body into a ReadableStream
. I was then passing this ready-made request into ServerConnection.makeRequest
, but because the body had been turned into a ReadableStream
at the point of making the request, Chrome complains that duplex
must be set for requests of this type. This same error may occur in other browsers, I’m not sure.
As a workaround for this my JLfetch
function now looks like:
import createClient from 'openapi-fetch';
import { paths } from './schema/schema';
const JLfetch = async (info: Request) => {
const url = info.url;
const { method, body } = info;
const init: RequestInit = { method, body };
// seems in some browsers, body is turned into a stream
// see https://issues.chromium.org/issues/40237822#makechanges
if (body instanceof ReadableStream ) {
init.body = await info.text() // turn body back into a string
}
const settings = ServerConnection.makeSettings();
return ServerConnection.makeRequest(url, init, settings);
};
const settings = ServerConnection.makeSettings();
export const client = createClient<paths>({
baseUrl: URLExt.join(settings.baseUrl, 'jupyter_cassini'),
fetch: JLfetch
});
Which converts the ReadableStream
back into a string, before passing it to makeRequest
.
Which seems to be working.
The way ServerConnection.makeRequest
is currently implemented makes it hard to create your own Request
object with all the appropriate stuff to be valid, hence such a workaround is currently necessary.
I hope this might be helpful to someone at some point!
Caveat here is that I’m not super familiar with web development, so I could be doing something very dumb here.
Cheers,
Hugh