axiosrequestserver-sent-eventshttp-streaming

ReadableStream with Axios not working in Safari, but working in Chrome and Firefox


I'm building a chat app with AI API like ChatGPT. I'm streaming back the responses, which works fine in chrome and firefox, but does not in Safari. There I always get this error:

[Log] Error: (handleError.ts, line 8)
AxiosError

cause: TypeError: ReadableByteStreamController is not implemented

column: 28

config: {transitional: {silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false}, adapter: "fetch", transformRequest: Array, transformResponse: Array, timeout: 0, …}

line: 1697

message: "ReadableByteStreamController is not implemented"

name: "TypeError"

request: Request

body: ReadableStream {locked: true}

bodyUsed: true

cache: "default"

credentials: "same-origin"

destination: ""

headers: Headers {append: function, delete: function, get: function, has: function, set: function, …}

integrity: ""

keepalive: false

method: "POST"

mode: "cors"

redirect: "follow"

referrer: "about:client"

referrerPolicy: ""

signal: AbortSignal {aborted: false, reason: undefined, onabort: null, throwIfAborted: function, addEventListener: function, …}

url: "http://localhost:3000/conversations/cht_GHyzT4yB35_363xN"

Request Prototyp

sourceURL: "http://localhost:5173/node_modules/.vite/deps/axios.js?v=e4786436"

stack: "AxiosError@http://localhost:5173/node_modules/.vite/deps/axios.js?v=e4786436:375:2…"

AxiosError Prototyp

For my API Calls, I'm using a wrapper around Axios, which makes the streaming request like this:

 async stream(data: any) {
        try {
            console.log("Sending request")
            const response = await instance.post(this.baseUrl, data, { headers: {
                'Accept': 'text/event-stream',
              },
              responseType: 'stream',
              adapter: 'fetch'
             });
            // Handle response data
            console.log("Request sent")
            return { data: response.data, error: null };
        } catch (error) {
            // Handle errors
            const handledError = await handleError(error);
            return { data: null, error: handledError };
        }
    }

With this, the "Request Sent" does not get logged to the console, so the error has to be in the post request.

The request on safari finished quickly, probably with the first chunk of data coming in. in the api logs it says for requests with safari they only take a couple ms to finish.


Solution

  • I have found a workaround using the browser native fetch() library, that works fine. Issue seems to be with Axios.

    EDIT: This is the working workaround

    async *stream(data: any) {
      try {
        console.log('Sending request');
        const response = await fetch(this.baseUrl, {
          method: 'POST',
          headers: {
            Authorization: 'Bearer ' + token
            Accept: 'text/event-stream',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data),
        });
    
        if (response.body) {
          const reader = response.body.getReader();
          const decoder = new TextDecoder('utf-8');
    
          while (true) {
            const { done, value } = await reader.read();
            if (done) {
              break;
            }
            const chunk = decoder.decode(value, { stream: true });
            yield chunk;
          }
        }
    
        console.log('Request sent');
      } catch (error) {
        const handledError = await handleError(error);
        yield { error: handledError };
      }
    }
    

    And then use this function to access the stream:

    async function handleStream() {
      const streamGenerator = await api
        .from('conversations')
        .byId(conversation.id)
        .stream({
          input
        });
    
      for await (const chunk of streamGenerator) {
        if (chunk.error) {
          // Handle error
          console.error(chunk.error);
          break;
        }
        // Process the chunk
        console.log(chunk);
        // Update your UI with the new chunk of data
      }
    }