Skip to content

React Native SSE is always moving to 'error' but the API is working in web SSE and postman #69

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

Closed
DeepikaSharma5 opened this issue Nov 12, 2024 · 4 comments

Comments

@DeepikaSharma5
Copy link

I am trying o implement react-native-sse in my react native application. I have converted the code from the web application which is already implemented using @microsoft/fetch-event-source. In there it is working without any issues.

But when I try to do the same implementation in react native app using react-native-sse it is always going to error. I wonder if I have missed anything or implemented anything wrong in the react-native code. I am passing all the values and necessary things as passed in the web application (values message and attachments are not empty and passing the value from props). I am new to sse and this is my first time implementing.

This is my coding for reference =>

`import 'react-native-get-random-values';
import {v4} from 'uuid';
import EventSource, {EventSourceListener} from 'react-native-sse';
import {BaseURL} from '../../utils/serviceURL';
import {getUserToken} from '../../asyncStorage/dataStore';
import {DeviceEventEmitter} from 'react-native';

const ABORT_STREAM_EVENT = 'abortStreamEvent'; // Define the abort event constant

export async function baseHeaders(): Promise<{Authorization: string | null}> {
  const basicAuth = await getUserToken();
  return {
    Authorization: basicAuth ? 'Bearer ${basicAuth}' : null,
  };
}

export const multiplexStream = async function ({
  workspaceSlug,
  threadSlug = null,
  prompt,
  chatHandler,
  attachments = [],
}: {
  workspaceSlug: string;
  threadSlug?: string | null;
  prompt: string;
  chatHandler: (chat: any) => void;
 attachments?: any[];
}) {
  if (threadSlug) {
    return streamChat({slug: threadSlug}, prompt, chatHandler, attachments);
  }
  return streamChat({slug: workspaceSlug}, prompt, chatHandler, attachments);
};

export const streamChat = async function (
  {slug}: {slug: string},
  message: string,
  handleChat: (chat: any) => void,
  attachments: any[] = [],
) {

  const ctrl = new AbortController();

  // Listen for the ABORT_STREAM_EVENT key to be emitted by the client
  const subscription = DeviceEventEmitter.addListener(
    ABORT_STREAM_EVENT,
    () => {
      ctrl.abort();
      handleChat({id: v4(), type: 'stopGeneration'});
    },
  );

  // Create the EventSource instance
  const eventSource = new EventSource(
    `${BaseURL}workspace/${slug}/stream-chat`,
    {
      method: 'POST',
      headers: await baseHeaders(),
     body: JSON.stringify({message, attachments}),
    },
  );

  const listener: EventSourceListener = (event: any) => {
    if (event.type === 'open') {
  console.log('Open SSE connection.');
  if (event.ok) {
    return; // everything's good
  } else if (
    event.xhrStatus >= 400 &&
    event.xhrStatus < 500 &&
    event.xhrStatus !== 429
  ) {
    handleChat({
      id: v4(),
      type: 'abort',
      textResponse: null,
      sources: [],
      close: true,
      error: `An error occurred while streaming response. Code ${
        event.xhrStatus
      }. Message - ${JSON.parse(event.message).error}`,
    });
    ctrl.abort();
    throw new Error('Invalid Status code response.');
  } else {
    handleChat({
      id: v4(),
      type: 'abort',
      textResponse: null,
      sources: [],
      close: true,
      error: `An error occurred while streaming response. Unknown Error.`,
    });
    ctrl.abort();
    throw new Error('Unknown error');
  }
} else if (event.type === 'message') {
  try {
    const chatResult = JSON.parse(event.data);
    handleChat(chatResult);
  } catch (error) {
    console.error('Failed to parse message:', error);
  }
} else if (event.type === 'error') {
  const data = JSON.parse(event.message);
  handleChat({
    id: data.id || v4(),
    type: data.type || 'abort',
    textResponse: data.textResponse || null,
    sources: data.source || [],
    close: data.close || true,
    error: `An error occurred while streaming response.  Code ${
      event.xhrStatus
    }. Message - ${JSON.parse(event.message).error}`,
  });
  ctrl.abort();
} else if (event.type === 'exception') {
  console.error('Error:', JSON.parse(event.message).error, event.xhrStatus);
}
   };

  eventSource.addEventListener('open', listener);
  eventSource.addEventListener('message', listener);
  eventSource.addEventListener('error', listener);

  return () => {
    eventSource.close();
    subscription.remove();
  };
};`

If anyone can help me identify if I have done something wrong or may be I may need to make any changes in the server side?

In the react application, this is how it is implemented =>

`multiplexStream: async function ({
    workspaceSlug,
    threadSlug = null,
    prompt,
    chatHandler,
    attachments = [],
  }) {
    if (!!threadSlug)
      return this.threads.streamChat(
        { workspaceSlug, threadSlug },
        prompt,
        chatHandler,
        attachments
      );
    return this.streamChat(
      { slug: workspaceSlug },
      prompt,
      chatHandler,
      attachments
    );
  },
  streamChat: async function ({ slug }, message, handleChat, attachments = []) {
    const ctrl = new AbortController();

// Listen for the ABORT_STREAM_EVENT key to be emitted by the client
// to early abort the streaming response. On abort we send a special `stopGeneration`
// event to be handled which resets the UI for us to be able to send another message.
// The backend response abort handling is done in each LLM's handleStreamResponse.
window.addEventListener(ABORT_STREAM_EVENT, () => {
  ctrl.abort();
  handleChat({ id: v4(), type: "stopGeneration" });
});

await fetchEventSource(`${API_BASE}/workspace/${slug}/stream-chat`, {
  method: "POST",
  body: JSON.stringify({ message, attachments }),
  headers: baseHeaders(),
  signal: ctrl.signal,
  openWhenHidden: true,
  async onopen(response) {
    if (response.ok) {
      return; // everything's good
    } else if (
      response.status >= 400 &&
      response.status < 500 &&
      response.status !== 429
    ) {
      handleChat({
        id: v4(),
        type: "abort",
        textResponse: null,
        sources: [],
        close: true,
        error: `An error occurred while streaming response. Code ${response.status}`,
      });
      ctrl.abort();
      throw new Error("Invalid Status code response.");
    } else {
      handleChat({
        id: v4(),
        type: "abort",
        textResponse: null,
        sources: [],
        close: true,
        error: `An error occurred while streaming response. Unknown Error.`,
      });
      ctrl.abort();
      throw new Error("Unknown error");
    }
  },
  async onmessage(msg) {
    try {
      const chatResult = JSON.parse(msg.data);
      handleChat(chatResult);
    } catch {}
  },
  onerror(err) {
    handleChat({
      id: v4(),
      type: "abort",
      textResponse: null,
      sources: [],
      close: true,
      error: `An error occurred while streaming response. ${err.message}`,
    });
    ctrl.abort();
    throw new Error();
  },
});

},`

Any help would be really helpful as I am trying this for more than a week to identify what the issue is. As the react application is working, backend developer are not supporting to debug or change the code.

It is working fine in the postman too =>
image

@EmilJunker
Copy link
Contributor

Please pass the debug: true option when creating the event source instance and share the logs with us so we can see what is going on under the hood.

  // Create the EventSource instance
  const eventSource = new EventSource(
    `${BaseURL}workspace/${slug}/stream-chat`,
    {
+     debug: true,
      method: 'POST',
      headers: await baseHeaders(),
      body: JSON.stringify({message, attachments}),
    },
  );

@DeepikaSharma5
Copy link
Author

Once I turn on the debug mode these are the debug logs I received

DEBUG [EventSource] Will open new connection in 500 ms.
DEBUG [EventSource][onreadystatechange] ReadyState: HEADERS_RECEIVED(2), status: 400
DEBUG [EventSource][onreadystatechange] ReadyState: LOADING(3), status: 400
DEBUG [EventSource][onreadystatechange] ReadyState: DONE(4), status: 400
DEBUG [EventSource][onreadystatechange][ERROR] Response status error.

@EmilJunker
Copy link
Contributor

It's not entirely clear from the logs, but I suspect the headers react-native-sse is sending by default are causing the problem. There is an open pull request aimed at making the default headers optional: #51

@DeepikaSharma5 For now, you can try going to the node_modules/react-native-sse folder and removing lines 80 and 81 from the source code.

this._xhr.setRequestHeader('Cache-Control', 'no-cache');
this._xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

@DeepikaSharma5
Copy link
Author

Thank you. This helped me fix the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants