nats-io / nats.ws

WebSocket NATS
Apache License 2.0
329 stars 29 forks source link

React Native - URL.protocol is not implemented #195

Closed CrisRonda closed 3 weeks ago

CrisRonda commented 1 year ago

I had this hook to connect to nats server. I am working with TS and React Native. I already tried this The error I see in the console is: [ERROR: URL.protocol is not implemented]

import {connect, NatsConnection} from 'nats.ws';

import {useEffect, useState} from 'react';

export default function useMessages() {
  const [nats, setNats] = useState<NatsConnection>();

  useEffect(() => {
    (async () => {
      const nc = await connect({
        servers: ['wss://demo.nats.io:8443'],
      });
      setNats(nc);
      console.log('connected to NATS');
    })();

    return () => {
      nats?.drain();
      console.log('closed NATS connection');
    };
  }, []);

  return {};
}

React Native info

System:
    OS: macOS 13.2.1
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 112.41 MB / 16.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.13.2 - ~/.nvm/versions/node/v16.13.2/bin/node
    Yarn: 1.22.15 - /usr/local/bin/yarn
    npm: 8.1.2 - ~/.nvm/versions/node/v16.13.2/bin/npm
    Watchman: Not Found
  Managers:
    CocoaPods: 1.11.3 - /Users/user/.rvm/gems/ruby-2.7.5/bin/pod
  SDKs:
    iOS SDK:
      Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4
    Android SDK:
      API Levels: 23, 24, 27, 28, 29, 30, 31, 33
      Build Tools: 28.0.3, 29.0.2, 29.0.3, 30.0.2, 30.0.3, 31.0.0
      System Images: android-25 | Google APIs Intel x86 Atom, android-27 | Google Play Intel x86 Atom, android-29 | Google APIs Intel x86 Atom, android-29 | Google Play Intel x86 Atom, android-30 | Google APIs Intel x86 Atom, android-30 | Google APIs Intel x86 Atom_64, android-31 | Google APIs Intel x86 Atom_64, android-33 | Google APIs Intel x86 Atom_64
      Android NDK: Not Found
  IDEs:
    Android Studio: 2020.3 AI-203.7717.56.2031.7678000
    Xcode: 14.3/14E222b - /usr/bin/xcodebuild
  Languages:
    Java: 11.0.2 - /Users/user/.jenv/shims/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 18.1.0 => 18.1.0 
    react-native: 0.70.5 => 0.70.5 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

nats.ws version: 1.14.0

aricart commented 1 year ago

Effectively the react native is unlikely to work - nats.ws is expecting a compliant ES w3c websocket environment.

jfols commented 1 year ago

Can you polyfill this with https://www.npmjs.com/package/react-native-url-polyfill?

aricart commented 1 year ago

you can try - I know that there are a few folks that have gotten older versions to work, but it is not clear if they got https://github.com/nats-io/nats.js to work or if it was https://github.com/nats-io/nats.ws.

jfols commented 1 year ago

@aricart is there a recommended approach for integrating NATS with React Native? Certainly this is a common ask, no?

aricart commented 1 year ago

It is not a very common ask, and it has been discussed that it should possibly be one of the supported clients.

CrisRonda commented 1 year ago

@jfols Work with that polyfill So I'm going put all steps to run this package in react native

  1. Run in your terminal
    yarn add fastestsmallesttextencoderdecoder nats.ws node-libs-react-native react-native-url-polyfill text-encoding-polyfill
  2. Update the metro.config.js like this:
    module.exports = {
    resolver: {
    extraNodeModules: require('node-libs-react-native'),
    },
    ...more config
    };
  3. In your index.js
    
    /**
    * Polyfills for nats.ws package
    */
    import 'react-native-url-polyfill/auto';
    import 'text-encoding-polyfill';

if (!Symbol.asyncIterator) { Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator'); }



And that's all 🌟 
pietgk commented 1 year ago

Hi, anyone any tips on what the status is regarding working with nats on react-native?

CrisRonda commented 1 year ago

Hi @pietgk I worked with nats and react native, so far I've not seen any problem. In order to use it: First follow the steps that I wrote Then you can create a hook to manage the subscriptions in the react native side, it could be some like this code:

import {connect, NatsConnection, consumerOpts} from 'nats.ws';

import {useCallback, useEffect, useRef} from 'react';
import {decode} from 'base-64';

const decodeMessageFromNatsServer = () => { 
    // YOUR LOGIC TO DECODE THE MESSAGE
}
const useNatsSubscription = ({
  table,
  onMessage,
}: {
  table: string;
  onMessage: (msg: object) => void;
}) => {
  const nats = useRef<NatsConnection>();

  const onSubscriptionMessage = useCallback(
    async ({nameSubscription}: {nameSubscription: string}) => {
      if (!nats.current) {
        return;
      }

      const opts = consumerOpts({
        name: 'MY_CHANNEL',
        description: 'MY_CHANNEL_DESC',
      });
      opts.deliverNew();
      opts.ackExplicit();
      opts.manualAck();
      nats.current
        ?.jetstream()
        .pullSubscribe(nameSubscription, opts)
        .then(async psub => {
          psub?.pull({batch: 1});
          for await (const m of psub) {
            const payload = decodeMessageFromNatsServer(m);
            if (payload) {
              onMessage(payload);
            }
            m.ack();
            psub.pull({batch: 1});
          }
        });
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [table],
  );

  const onSubscribe = useCallback(async () => {
    try {
      const nameSubscription = 'NAME_SUBSCRIPTION';
      if (!nameSubscription) {
        return;
      }
      // eslint-disable-next-line no-undef
      const encoder = new TextEncoder();
      const credentials = decode('NATS_USER_CRED_STRING');
      const encodedCredentials = encoder.encode(credentials);
      const nc = await connect({
        debug: false,
          servers: ['NATS_SERVER_ADDRESS'],
        authenticator: encodedCredentials,
      });
      nats.current = nc;
      await onSubscriptionMessage({nameSubscription});
    } catch (error) {
      console.log("ERROR: NATS Subscription", error);
    }
  }, []);

  useEffect(() => {
    onSubscribe();
    return async () => {
      await nats.current?.drain();
    };
  }, [onSubscribe]);

  return {
    onSubscribe,
  };
};

export default useNatsSubscription;

// IN YOU COMPONENT
  const sendedMessage = useRef(false);
const onRefresh = async () => {
    // YOUT LOGIC
}
 useNatsSubscription({
    table: 'message',
    onMessage: () => {
      /* This code is part of a subscription to a NATS message queue. When a new message is received, it
      checks if the `sendedMessage.current` flag is set to true. If it is, it means that the message
      was sent by the current user and there is no need to refresh the conversation. If it is not
      set, it calls the `onRefresh` function to refresh data with the new data/message. */
      const refresh = async () => {
        try {
          if (sendedMessage.current) {
            sendedMessage.current = false;
            return;
          }
          await onRefresh(false);
        } catch (error) {
        } finally {
          sendedMessage.current = false;
        }
      };

      refresh().catch(error => console.log('on refresh', error));
    },
  });
kindapath commented 11 months ago

@jfols Work with that polyfill So I'm going put all steps to run this package in react native

  1. Run in your terminal
yarn add fastestsmallesttextencoderdecoder nats.ws node-libs-react-native react-native-url-polyfill text-encoding-polyfill
  1. Update the metro.config.js like this:
module.exports = {
  resolver: {
    extraNodeModules: require('node-libs-react-native'),
  },
  ...more config
};
  1. In your index.js
/**
 * Polyfills for nats.ws package
 */
import 'react-native-url-polyfill/auto';
import 'text-encoding-polyfill';

if (!Symbol.asyncIterator) {
  Symbol.asyncIterator = Symbol.for('Symbol.asyncIterator');
}

And that's all 🌟

It works, thank you so much mate! I tried to develop my own TCP library, it worked, but was pretty unstable.

For other folks trying to fix this problem. You can follow steps above and start with this simple example to ensure that it works:

https://nats.io/blog/getting-started-nats-ws/#reactjs

Basically, once you are able to connect, you are good to go