TypeError: null is not an object (evaluating 'WebRTCModule.getUserMedia')

I’m building an React-native-app with openvidu, I’ve already went through the tutorial and even tested the demo, and made some changes to work as I needed, however, when I tried to move the exact same implementation to the actual app that I’m working on, I’m having this issue.

This is the code:

import React, {Component} from 'react';
import {
  Platform,
  TextInput,
  ScrollView,
  Button,
  StyleSheet,
  Text,
  View,
  Image,
  PermissionsAndroid,
} from 'react-native';
import InCallManager from 'react-native-incall-manager';

import {
  OpenViduReactNativeAdapter,
  OpenVidu,
  RTCView,
} from 'openvidu-react-native-adapter';

type Props = {};

export default class App extends Component<Props> {
  constructor(props) {
    super(props);

    const ovReact = new OpenViduReactNativeAdapter();
    ovReact.initialize();

    this.state = {
      mySessionId: '1233',
      myUserName: 'User1233',
      session: undefined,
      mainStreamManager: undefined,
      subscribers: [],
      role: 'PUBLISHER',
      mirror: true,
      token: undefined,
      videoSource: undefined,
      video: true,
      audio: true,
      speaker: false,
      joinBtnEnabled: true,
      isReconnecting: false,
      connected: false,
    };
  }

  componentDidMount() {
    //this.joinSession();
  }

  // componentWillUnmount() {
  // this.leaveSession();
  // }

  async checkAndroidPermissions() {
    try {
      const camera = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.CAMERA,
        {
          title: 'Camera Permission',
          message: 'OpenVidu needs access to your camera',
          buttonNeutral: 'Ask Me Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK',
        },
      );
      const audio = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
        {
          title: 'Audio Permission',
          message: 'OpenVidu needs access to your microphone',
          buttonNeutral: 'Ask Me Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK',
        },
      );
      const storage = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
        {
          title: 'STORAGE',
          message: 'OpenVidu  needs access to your storage ',
          buttonNeutral: 'Ask Me Later',
          buttonNegative: 'Cancel',
          buttonPositive: 'OK',
        },
      );
      if (camera === PermissionsAndroid.RESULTS.GRANTED) {
        console.log('You can use the camera');
      } else {
        console.log('Camera permission denied');
      }
      if (audio === PermissionsAndroid.RESULTS.GRANTED) {
        console.log('You can use the audio');
      } else {
        console.log('audio permission denied');
      }
      if (storage === PermissionsAndroid.RESULTS.GRANTED) {
        console.log('You can use the storage');
      } else {
        console.log('storage permission denied');
      }
    } catch (err) {
      console.warn(err);
    }
  }

  joinSession(role) {
    console.log('joinsession1');
    // --- 1) Get an OpenVidu object ---

    this.OV = new OpenVidu();
    this.OV.enableProdMode();

    // --- 2) Init a session ---

    this.setState(
      {
        joinBtnEnabled: false,
        session: this.OV.initSession(),
        role,
      },
      async () => {
        const mySession = this.state.session;
        // --- 3) Specify the actions when events take place in the session ---
        console.log('joinsession2');
        // On every new Stream received...
        mySession.on('streamCreated', async event => {
          // Subscribe to the Stream to receive it. Second parameter is undefined
          // so OpenVidu doesn't create an HTML video by its own
          const subscriber = await mySession.subscribeAsync(
            event.stream,
            undefined,
          );
          var subscribers = Array.from(this.state.subscribers);
          subscribers.push(subscriber);
          // Update the state with the new subscribers
          this.setState({
            subscribers: subscribers,
          });
        });

        // On every Stream destroyed...
        mySession.on('streamDestroyed', event => {
          event.preventDefault();
          // Remove the stream from 'subscribers' array
          this.deleteSubscriber(event.stream);
        });

        // On every asynchronous exception...
        mySession.on('exception', exception => {
          console.warn(exception);
        });

        // On reconnection events
        mySession.on('reconnecting', () => {
          console.warn('Oops! Trying to reconnect to the session');
          this.setState({isReconnecting: true});
        });

        mySession.on('reconnected', () => {
          console.log('Hurray! You successfully reconnected to the session');
          setTimeout(() => {
            // Force re-render view updating state avoiding frozen streams
            this.setState({isReconnecting: false});
          }, 2000);
        });
        mySession.on('sessionDisconnected', event => {
          if (event.reason === 'networkDisconnect') {
            console.warn('Dang-it... You lost your connection to the session');
            this.getout();
          } else {
            // Disconnected from the session for other reason than a network drop
          }
        });

        try {
          console.log('joinsession3');
          // --- 4) Connect to the session with a valid user token ---
          // 'getToken' method is simulating what your server-side should do.
          // 'token' parameter should be retrieved and returned by your own backend
          const token = await this.getToken();
          // First param is the token got from OpenVidu Server. Second param can be retrieved by every user on event
          // 'streamCreated' (property Stream.connection.data), and will be appended to DOM as the user's nickname
          this.state.token = token;
          await mySession.connect(this.state.token, {
            clientData: this.state.myUserName,
          });
          console.log('joinsession4');
          if (Platform.OS === 'android') {
            await this.checkAndroidPermissions();
          }
          console.log('joinsession5');
          // --- 5) Get your own camera stream ---
          if (this.state.role !== 'SUBSCRIBER') {
            console.log('joinsessionsub1');
            const properties = {
              audioSource: undefined, // The source of audio. If undefined default microphone
              videoSource: undefined, // The source of video. If undefined default webcam
              publishAudio: true, // Whether you want to start publishing with your audio unmuted or not
              publishVideo: true, // Whether you want to start publishing with your video enabled or not
              resolution: '640x480', // The resolution of your video
              frameRate: 30, // The frame rate of your video
              insertMode: 'APPEND', // How the video is inserted in the target element 'video-container'
            };

            // Init a publisher passing undefined as targetElement (we don't want OpenVidu to insert a video
            // element: we will manage it on our own) and with the desired propertiesç
            console.log('joinsessionsub2');
            const publisher = await this.OV.initPublisherAsync(
              undefined,
              properties,
            );
            // --- 6) Publish your stream ---
            console.log('joinsessionsub3');
            // Set the main video in the page to display our webcam and store our Publisher
            this.setState(
              {
                mainStreamManager: publisher,
                videoSource: !properties.videoSource
                  ? '1'
                  : properties.videoSource, // 0: back camera | 1: user camera |
              },
              () => {
                mySession.publish(publisher);
              },
            );
            console.log('joinsessionsub4');
          }
          this.setState({connected: true});
          console.log('joinsession ending');
        } catch (error) {
          console.log(
            'There was an error connecting to the session:',
            error.code,
            error.message,
          );
          this.setState({
            joinBtnEnabled: true,
          });
        }
      },
    );
  }

  getNicknameTag(stream) {
    // Gets the nickName of the user
    try {
      if (
        stream.connection &&
        JSON.parse(stream.connection.data) &&
        JSON.parse(stream.connection.data).clientData
      ) {
        return JSON.parse(stream.connection.data).clientData;
      }
    } catch (error) {}
    return '';
  }

  deleteSubscriber(stream) {
    var subscribers = Array.from(this.state.subscribers);
    const index = subscribers.indexOf(stream.streamManager, 0);
    if (index > -1) {
      subscribers.splice(index, 1);
      this.setState({
        subscribers: subscribers,
      });
    }
  }

  toggleCamera() {
    /**
     * _switchCamera() Method provided by react-native-webrtc:
     * This function allows to switch the front / back cameras in a video track on the fly, without the need for adding / removing tracks or renegotiating
     */

    const camera = this.state.mainStreamManager.stream
      .getMediaStream()
      .getVideoTracks()[0];
    if (camera) {
      camera._switchCamera();
      this.setState({mirror: !this.state.mirror});
    }

    /**
     * Traditional way:
     * Renegotiating stream and init new publisher to change the camera
     */
    /*
        this.OV.getDevices().then(devices => {
            console.log("DEVICES => ", devices);
            let device = devices.filter(device => device.kind === 'videoinput' && device.deviceId !== this.state.videoSource)[0]
            const properties = {
                audioSource: undefined,
                videoSource: device.deviceId,
                publishAudio: true,
                publishVideo: true,
                resolution: '640x480',
                frameRate: 30,
                insertMode: 'APPEND',
            }

            let publisher = this.OV.initPublisher(undefined, properties);

            this.state.session.unpublish(this.state.mainStreamManager);

            this.setState({
                videoSource : device.deviceId,
                mainStreamManager: publisher,
                mirror: !this.state.mirror
            });
            this.state.session.publish(publisher);
        });
        */
  }

  muteUnmuteMic() {
    this.state.mainStreamManager.publishAudio(!this.state.audio);
    this.setState({audio: !this.state.audio});
  }

  muteUnmuteCamera() {
    this.state.mainStreamManager.publishVideo(!this.state.video);
    this.setState({video: !this.state.video});
  }

  muteUnmuteSpeaker() {
    InCallManager.setSpeakerphoneOn(!this.state.speaker);
    this.setState({speaker: !this.state.speaker});
  }

  render() {
    return (
      <ScrollView>
        {this.state.connected ? (
          <View>
            {this.state.mainStreamManager &&
              this.state.mainStreamManager.stream && (
                <View style={styles.container}>
                  <Text>Session: {this.state.mySessionId}</Text>
                  <Text>
                    {this.getNicknameTag(this.state.mainStreamManager.stream)}
                  </Text>
                  <RTCView
                    zOrder={0}
                    objectFit="cover"
                    mirror={this.state.mirror}
                    streamURL={this.state.mainStreamManager.stream
                      .getMediaStream()
                      .toURL()}
                    style={styles.selfView}
                  />
                </View>
              )}

            <View>
              <View style={styles.button}>
                <Button
                  disabled={this.state.role === 'SUBSCRIBER'}
                  onLongPress={() => this.toggleCamera()}
                  onPress={() => this.toggleCamera()}
                  title="Toggle Camera"
                  color="#841584"
                />
              </View>

              <View style={styles.button}>
                <Button
                  disabled={this.state.role === 'SUBSCRIBER'}
                  onLongPress={() => this.muteUnmuteMic()}
                  onPress={() => this.muteUnmuteMic()}
                  title={
                    this.state.audio ? 'Mute Microphone' : 'Unmute Microphone'
                  }
                  color="#3383FF"
                />
              </View>
              <View style={styles.button}>
                <Button
                  disabled={this.state.role === 'SUBSCRIBER'}
                  onLongPress={() => this.muteUnmuteSpeaker()}
                  onPress={() => this.muteUnmuteSpeaker()}
                  title={this.state.speaker ? 'Mute Speaker' : 'Unmute Speaker'}
                  color="#79b21e"
                />
              </View>
              <View style={styles.button}>
                <Button
                  disabled={this.state.role === 'SUBSCRIBER'}
                  onLongPress={() => this.muteUnmuteCamera()}
                  onPress={() => this.muteUnmuteCamera()}
                  title={this.state.video ? 'Mute Camera' : 'Unmute Camera'}
                  color="#00cbff"
                />
              </View>

              <View style={styles.button}>
                <Button
                  onLongPress={() => this.getout()}
                  onPress={() => this.getout()}
                  title="Leave Session"
                  color="#ff0000"
                />
              </View>
            </View>
          </View>
        ) : (
          <View>
            <View
              style={{
                justifyContent: 'center',
                alignItems: 'center',
                padding: 20,
              }}>
              <Image
                style={styles.img}
                source={require('../../assets/TestImage.png')}
              />
            </View>
            <View style={{justifyContent: 'center', alignItems: 'center'}}>
              <TextInput
                style={{
                  width: '90%',
                  height: 40,
                  borderColor: 'gray',
                  borderWidth: 1,
                }}
                onChangeText={mySessionId => this.setState({mySessionId})}
                value={this.state.mySessionId}
              />
            </View>

            <View style={styles.button}>
              <Button
                disabled={!this.state.joinBtnEnabled}
                onLongPress={() => this.joinSession('PUBLISHER')}
                onPress={() => this.joinSession('PUBLISHER')}
                title="Join as publisher"
                color="#841584"
              />
            </View>

            <View style={styles.button}>
              <Button
                disabled={!this.state.joinBtnEnabled}
                onLongPress={() => this.joinSession('SUBSCRIBER')}
                onPress={() => this.joinSession('SUBSCRIBER')}
                title="Join as subscriber"
                color="#00cbff"
              />
            </View>
          </View>
        )}

        <View
          style={[styles.container, {flexDirection: 'row', flexWrap: 'wrap'}]}>
          {this.state.subscribers.map((item, index) => {
            return (
              <View key={index}>
                <Text>{this.getNicknameTag(item.stream)}</Text>
                <RTCView
                  zOrder={0}
                  objectFit="cover"
                  style={styles.remoteView}
                  streamURL={item.stream.getMediaStream().toURL()}
                />
              </View>
            );
          })}
        </View>
      </ScrollView>
    );
  }

  /**
   * --------------------------
   * SERVER-SIDE RESPONSIBILITY
   * --------------------------
   * These methods retrieve the mandatory user token from OpenVidu Server.
   * This behavior MUST BE IN YOUR SERVER-SIDE IN PRODUCTION (by using
   * the API REST, openvidu-java-client or openvidu-node-client):
   *   1) Initialize a Session in OpenVidu Server	(POST /openvidu/api/sessions)
   *   2) Create a Connection in OpenVidu Server (POST /openvidu/api/sessions/<SESSION_ID>/connection)
   *   3) The Connection.token must be consumed in Session.connect() method
   */

  getToken() {
    // return this.createSession(this.state.mySessionId)
    // 	.then((sessionId) => this.createToken(sessionId))
    // 	.catch((error) => console.log(error));
    if (this.state.role !== 'SUBSCRIBER') {
      console.log('criar');
      return this.createToken().catch(error => console.log(error));
    } else {
      console.log('se juntar');
      return this.createTokenSubscribe().catch(error => console.log(error));
    }
  }

  getout() {
    if (this.state.role !== 'SUBSCRIBER') {
      console.log(this.state.role);
      this.leaveSession();
    } else {
      console.log(this.state.role);
      this.leaveSessionSubscriver();
    }
  }

  leaveSession() {
    // --- 7) Leave the session by calling 'disconnect' method over the Session object ---

    // const mySession = this.state.session;

    // if (mySession) {
    // 	mySession.disconnect();
    // }

    // // Empty all properties...
    // setTimeout(() => {
    // 	this.OV = null;
    // 	this.setState({
    // 		session: undefined,
    // 		subscribers: [],
    // 		mySessionId: 'testReact',
    // 		myUserName: 'Participant' + Math.floor(Math.random() * 100),
    // 		mainStreamManager: undefined,
    // 		publisher: undefined,
    // 		joinBtnEnabled: true,
    // 		connected: false,
    // 	});
    // });
    console.log('leaving');
    const options = {
      method: 'POST',
      headers: {
        'User-Id': '1234',
      },
    };
    const url = `https://svc-broadcast.dev.comprealugueagora.com/broadcast/finish/11115`;

    fetch(url, options)
      .then(() => {
        console.log('leaving finally');
        console.log(url);
        const mySession = this.state.session;

        if (mySession) {
          mySession.disconnect();
        }

        // Empty all properties...
        setTimeout(() => {
          this.OV = null;
          this.setState({
            session: undefined,
            subscribers: [],
            mySessionId: '1234',
            myUserName: 'user1234',
            mainStreamManager: undefined,
            publisher: undefined,
            joinBtnEnabled: true,
            connected: false,
          });
        });
      })
      .catch(error => {
        console.log('error leaving');
        console.log(url);
        console.error(error);
      });
  }

  leaveSessionSubscriver() {
    // --- 7) Leave the session by calling 'disconnect' method over the Session object ---

    // const mySession = this.state.session;

    // if (mySession) {
    // 	mySession.disconnect();
    // }

    // // Empty all properties...
    // setTimeout(() => {
    // 	this.OV = null;
    // 	this.setState({
    // 		session: undefined,
    // 		subscribers: [],
    // 		mySessionId: 'testReact',
    // 		myUserName: 'Participant' + Math.floor(Math.random() * 100),
    // 		mainStreamManager: undefined,
    // 		publisher: undefined,
    // 		joinBtnEnabled: true,
    // 		connected: false,
    // 	});
    // });
    console.log('leaving');
    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        token: this.state.token,
      }),
    };
    const url = `https://svc-broadcast.dev.comprealugueagora.com/broadcast/leave/11115`;

    fetch(url, options)
      .then(() => {
        console.log('leaving finally');
        console.log(url);
        const mySession = this.state.session;

        if (mySession) {
          mySession.disconnect();
        }

        // Empty all properties...
        setTimeout(() => {
          this.OV = null;
          this.setState({
            session: undefined,
            subscribers: [],
            mySessionId: '1234',
            myUserName: 'user1234',
            mainStreamManager: undefined,
            publisher: undefined,
            joinBtnEnabled: true,
            connected: false,
          });
        });
      })
      .catch(error => {
        console.log('error leaving');
        console.log(url);
        console.error(error);
      });
  }

  createToken() {
    const testepublish = `https://svc-broadcast.dev.comprealugueagora.com/broadcast/start/11115`;
    console.log('creating token');
    // return new Promise((resolve, reject) => {
    // 	var data = JSON.stringify({ role: this.state.role });
    // 	axios
    // 		.post(teste, {
    // 			headers: {
    // 				'User-Id': '3214',
    // 			},
    // 		})
    // 		.then((response) => {
    // 			console.log('TOKEN CREATED: ', response?.presenter?.token);
    // 			resolve(response?.presenter?.token);
    // 		})
    // 		.catch((error) => {
    // 			console.log('error creating token');
    // 			console.error(error);
    // 			reject(error);
    // 		});
    // });

    const options = {
      method: 'POST',
      headers: {
        'User-Id': '1234',
      },
    };
    return new Promise((resolve, reject) => {
      fetch(testepublish, options)
        .then(res => res.json())
        .then(data => {
          console.log('TOKEN CREATED: ', data?.presenter?.token);
          console.log(testepublish);
          resolve(data?.presenter?.token);
        })
        .catch(error => {
          console.log('error creating token');
          console.log(testepublish);
          console.error(error);
          reject(error);
        });
    });
  }

  createTokenSubscribe() {
    const testejoin = `https://svc-broadcast.dev.comprealugueagora.com/broadcast/join/11115`;
    console.log('creating token');
    // return new Promise((resolve, reject) => {
    // 	var data = JSON.stringify({ role: this.state.role });
    // 	axios
    // 		.post(teste, {
    // 			headers: {
    // 				'User-Id': '3214',
    // 			},
    // 		})
    // 		.then((response) => {
    // 			console.log('TOKEN CREATED: ', response?.presenter?.token);
    // 			resolve(response?.presenter?.token);
    // 		})
    // 		.catch((error) => {
    // 			console.log('error creating token');
    // 			console.error(error);
    // 			reject(error);
    // 		});
    // });

    const options = {
      method: 'POST',
      headers: {
        'User-Id': '123',
      },
    };
    return new Promise((resolve, reject) => {
      fetch(testejoin, options)
        .then(res => res.json())
        .then(data => {
          console.log('TOKEN CREATED: ', data?.token);
          console.log(testejoin);
          resolve(data?.token);
        })
        .catch(error => {
          console.log('error creating token');
          console.log(testejoin);
          console.error(error);
          reject(error);
        });
    });
  }
}

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
    paddingTop: Platform.OS == 'ios' ? 20 : 0,
  },
  selfView: {
    width: '100%',
    height: 300,
  },
  remoteView: {
    width: 150,
    height: 150,
  },
  button: {
    padding: 10,
  },
  img: {
    flex: 1,
    width: 400,
    height: 200,
  },
});

To make it simple, the specific part where the issue happened was here:


const publisher = await this.OV.initPublisherAsync(
              undefined,
              properties,
            );

The company I work for already purchased the license to use the framework, so there is no issue there, for more information, this is my package.json:

{
  "name": "iruas",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "install-ov": "npm i openvidu-react-native-adapter-*.tgz",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
  },
  "dependencies": {
    "@asgardeo/auth-js": "^2.0.12",
    "@caa/c2-core-react": "^1.0.100",
    "@ptomasroos/react-native-multi-slider": "^2.2.2",
    "@react-native-async-storage/async-storage": "^1.17.6",
    "@react-native-community/netinfo": "^7.1.7",
    "@react-navigation/native": "^6.0.6",
    "@react-navigation/native-stack": "^6.2.5",
    "@types/text-encoding": "^0.0.36",
    "appcenter": "4.4.4",
    "appcenter-analytics": "4.4.4",
    "appcenter-crashes": "4.4.4",
    "axios": "^0.27.2",
    "babel-plugin-module-resolver": "^4.1.0",
    "base-64": "^1.0.0",
    "crypto-js": "^4.1.1",
    "events": "^3.3.0",
    "intl": "^1.2.5",
    "jsrsasign": "^10.5.26",
    "jwt-decode": "^3.1.2",
    "moment": "^2.29.3",
    "openvidu-react-native-adapter": "file:openvidu-react-native-adapter-2.22.0.tgz",
    "node-abort-controller": "^3.0.1",
    "react": "17.0.2",
    "react-native": "0.68.2",
    "react-native-app-intro-slider": "^4.0.4",
    "react-native-config": "^1.4.6",
    "react-native-create-thumbnail": "^1.5.1",
    "react-native-dotenv": "^3.3.1",
    "react-native-exit-app": "^1.1.0",
    "react-native-fetch-api": "^3.0.0",
    "react-native-geocoding": "^0.5.0",
    "react-native-geolocation-service": "^5.3.0-beta.4",
    "react-native-get-random-values": "^1.8.0",
    "react-native-incall-manager": "^4.0.0",
    "react-native-infinite-looping-scroll": "0.0.4",
    "react-native-link-preview": "^1.4.2",
    "react-native-mask-input": "^1.1.1",
    "react-native-permissions": "^3.2.0",
    "react-native-polyfill-globals": "^3.1.0",
    "react-native-push-notification": "^8.1.1",
    "react-native-reanimated": "^2.9.1",
    "react-native-safe-area-context": "^3.3.2",
    "react-native-screens": "^3.10.1",
    "react-native-svg": "^12.1.1",
    "react-native-svg-transformer": "^0.20.0",
    "react-native-toast-notifications": "^3.2.3",
    "react-native-url-polyfill": "^1.3.0",
    "react-native-uuid": "^2.0.1",
    "react-native-video": "^5.2.0",
    "react-native-video-player": "^0.12.0",
    "react-native-vision-camera": "^2.13.5",
    "react-native-webview": "^11.18.2",
    "react-redux": "^8.0.2",
    "redux": "^4.2.0",
    "redux-thunk": "^2.4.1",
    "rxjs": "^7.5.6",
    "styled-components": "^5.3.5",
    "text-encoding": "^0.7.0",
    "url": "^0.11.0",
    "url-search-params-polyfill": "^8.1.1",
    "vision-camera-image-labeler": "^0.1.6",
    "web-streams-polyfill": "^3.2.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "@types/jest": "^26.0.23",
    "@types/node": "^17.0.42",
    "@types/react-native": "^0.67.3",
    "@types/react-native-vector-icons": "6.4.10",
    "@types/react-redux": "^7.1.24",
    "@types/react-test-renderer": "^17.0.1",
    "@types/redux": "^3.6.0",
    "@types/redux-thunk": "^2.1.0",
    "@typescript-eslint/eslint-plugin": "^5.17.0",
    "@typescript-eslint/parser": "^5.17.0",
    "babel-jest": "^26.6.3",
    "eslint": "^7.32.0",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.67.0",
    "react-test-renderer": "17.0.2",
    "typescript": "^4.4.4"
  },
  "resolutions": {
    "@types/react": "^17"
  },
  "jest": {
    "preset": "react-native",
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

I’ve tried everything, the token is working fine in the API, because I’ve already tested on the demo, and it has to be something about the dependencies since it is the exact same implementation from the demo I’ve changed and tested, did someone have gone through this issue?

Are you saying that the official tutorial works as expected and your own application doesn’t right? In this case, we can’t help you because of we don’t provide support to custom applications.
Please, review the tutorial thoroughly and check what is happen in your code.

Regards