Unable to subscribe on topic using #stomp/stompjs - javascript

Here is a part of my React component:
import React from 'react';
import { Client } from '#stomp/stompjs';
class Balance extends React.Component {
componentDidMount() {
const client = new Client({
brokerURL: 'ws://localhost:8080/stomp',
debug: (str) => {
console.log(str);
},
});
client.onConnect(() => {
console.log('onConnect');
client.subscribe('/topic/balance', message => {
console.log(message);
})
});
client.activate();
}
...
It looks like connection was established according to the debug output to browser's console:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:10000,10000
Received data
<<< CONNECTED
heart-beat:0,0
version:1.2
content-length:0
connected to server undefined
However, I don't see a message 'onConnect' in console, which means client.onConnect was never fired.
Therefore I can't subscribe to a topic.
What could be a problem here?
UPDATE:

According to author it was a mix up in syntax of the library.
The corrected code from my question look as the following:
import React from 'react';
import { Client } from '#stomp/stompjs';
class Balance extends React.Component {
componentDidMount() {
// The compat mode syntax is totally different, converting to v5 syntax
// Client is imported from '#stomp/stompjs'
this.client = new Client();
this.client.configure({
brokerURL: 'ws://localhost:8080/stomp',
onConnect: () => {
console.log('onConnect');
client.subscribe('/topic/balance', message => {
console.log(message);
})
},
// Helps during debugging, remove in production
debug: (str) => {
console.log(new Date(), str);
}
});
this.client.activate();
}
...
I created a full working example in my repo.

Related

GraphQL resolver context working in Playground but not client in official NextJS starter example

I'm reconfiguring my NextJS/Apollo app to allow for SSG with GraphQL API routes, and I'm using this official NextJS starter example as a base for the client config.
I've run into an interesting issue though in my own app, so I've went back to starter example and tried to reproduce it, and was able to. The issue is that without any context object passed into the query resolvers, everything works fine (in the playground and on the client). However, when you introduce a context object and pass it to the resolvers, it works fine in the playground but the context object is undefined when fired from the client. This is the code from the official NextJS starter example, I'll comment where I've added anything.
graphql.js
import { ApolloServer } from "apollo-server-micro";
import { schema } from "../../apollo/schema";
const apolloServer = new ApolloServer({
schema,
context: { //
foo: "bar", // this is the context object I've added
}, //
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
typedefs.js
import { gql } from '#apollo/client'
export const typeDefs = gql`
type User {
id: ID!
name: String!
status: String!
}
type Query {
viewer: User
}
`
schema.js
import { makeExecutableSchema } from 'graphql-tools'
import { typeDefs } from './type-defs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
resolvers.js
export const resolvers = {
Query: {
viewer: (_parent, _args, context, _info) => {
console.log("context", context); // console log check that I've added
return { id: 1, name: "John Smith", status: "cached" };
},
},
};
When I run this in the GraphQL playground and query the API, it gives me the correct response, and in my terminal console it returns the context foo: bar object from the console log, so in the server the context object is being passed correctly. However, when I visit the index page in the browser, which is this:
index.js
import gql from "graphql-tag";
import Link from "next/link";
import { useQuery } from "#apollo/client";
import { initializeApollo } from "../apollo/client";
const ViewerQuery = gql`
query ViewerQuery {
viewer {
id
name
status
}
}
`;
const Index = () => {
const {
data: { viewer },
} = useQuery(ViewerQuery);
return (
<div>
You're signed in as {viewer.name} and you're {viewer.status} goto{" "}
<Link href="/about">
<a>static</a>
</Link>{" "}
page.
</div>
);
};
export async function getStaticProps() {
const apolloClient = initializeApollo();
await apolloClient.query({
query: ViewerQuery,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
}
export default Index;
...the viewer name and viewer status are rendered, so the query is actually happening, but in the console, the context object console log is returning undefined. So when used in the client, the context is being lost somehow. I find this interesting, since this is an official NextJS starter example, and unless they've set up the client to not accept context in the resolvers, I can't see what the problem is. And if it is the case that the client is not set up to accept context, is there any other official examples with a client setup that does?
This is a long question now, but here is the client.js setup:
import { useMemo } from "react";
import { ApolloClient, InMemoryCache } from "#apollo/client";
let apolloClient;
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
I implore anyone to clone this official repo and see if they can figure out how to get context working in the client, or if anyone knows why this client setup isn't working for context and knows a client setup that does accept resolver context, I would appreciate it. This problem has cost me two days now!
I've figured out the problem. The client configuration is using SchemaLink for the http request, and the context is passed in the SchemaLink constructor function, not in the server options, because context is passed in the http headers with httpLink.

Referencing a Vue component (from a socket function)

so what I am currently working on is using vue with socketIO. So I want a component that updates its data when the websocket emits something.
I looked at the following tutorial: https://medium.com/#michaelmangial1/getting-started-with-vue-js-socket-io-8d385ffb9782.
The connection to the websocket works fine however I don't know how I can change a data variable of the component or call a method of the component from the websocket function as I get the error that e.g. this.getAllLayouts is not a function.
In the tutorial they just set app.title = data.title; in socket.on() as the Vue instance can be addresses as app.
But now I am confused. I wanted to use this in an component which I defined as a single-file component. So I can't use this app reference. But how can I reference the component in that case?
Below you can see the most important parts of this component. I defined the socket connection in the created() of the component
<script>
import axios from 'axios';
// eslint-disable-next-line
import io from 'socket.io-client';
import FirstPage from './FirstPage.vue';
import DNDAssign from './DNDAssign.vue';
export default {
name: 'ControlCenter',
data() {
return {
devices: [{ ip: 'yet unknown' }], // placeholder so line 12 does not throw error before actual device info fetched
thisDeviceIndex: 0,
currentLayoutIndex: 0,
layouts: [],
};
},
components: {
DNDAssign,
FirstPage,
},
methods: {
getAllLayouts() {
const path = 'http://192.168.0.38:5000/layouts';
axios.get(path)
.then((response) => {
console.log(response);
this.layouts = response.data.layouts;
this.devices = response.data.devices;
this.thisDeviceIndex = response.data.your_device_index;
console.log(this.layouts);
})
.catch((error) => {
// eslint-disable-next-line
console.log(error);
});
},
},
created() {
// inital fetching of layouts
console.log('fetching layouts from backend');
this.getAllLayouts();
// test websocket connection
const socket = io.connect('http://192.168.0.38:5000');
// eslint-disable-next-line
socket.on('connect', function () {
console.error('connected to webSocket');
socket.emit('my event', { data: 'I\'m connected!' });
});
// eslint-disable-next-line
socket.on('my response', function(data){
console.log('got response');
console.log(data.data);
});
// eslint-disable-next-line
socket.on('update_on_layouts', function(data){
this.getAllLayouts(); //this does not work? How can I make it work or do something like
// this.layouts = data;
});
},
};
</script>
Issue is that this inside that fn is not binded to Vue, you need to lexically bind it
Use arrow method to bind this to method
socket.on('update_on_layouts', data => {
this.getAllLayouts(); //this does not work? How can I make it work or do something like
// this.layouts = data;
});

Find service promise fails in feathersJs

I am writing my first feathersjs app. Server side is fine, tested with postman and working perfectly. Client side I keep getting a failed promise on the 'find' service as it is out of the box. I am quite new to JS in general and very new to feathers, so probably have missed something very obvious. In the code below the TradeService.find always throws a failed promise.
I am inside a non-ejected CRA with mobx, but I don't think that is a problem. I have been watching tutorials and reading stuff.
I have a feathers.js file
import io from "#feathersjs/socketio-client";
import feathers from "#feathersjs/client";
const socket = io("http://localhost:3030");
const client = feathers().configure(feathers.socketio(socket));
const TradeService = client.service("trades");
export { client, TradeService };
Then the main file where it all goes wrong looks like this
import { TradeService } from "../../feathers";
#observer
export default class HeadlineReportPage extends Component {
constructor(props) {
super(props);
this.state = {
trades: []
};
}
componentDidMount() {
this.fetchFromServer();
}
fetchFromServer = async () => {
console.log("tradeService: " + TradeService);
await TradeService.find({
query: {
$limit: 0
}
})
.then(res => {
console.log("in then");
console.log("total is " + res.total);
setState({ trades: res.data });
})
.catch(res => console.log("Failed promise :" + res));
};
I stole most of this code from examples online, so was expecting it to just get to the 'then', but it goes to the catch every time. I have tried with and without the async/await.

How to import SignalR in React Component?

I have used create-react-app to scaffold the initial react application.
My DashBoard component:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import $ from 'jquery';
import 'signalr';
class Dashboard extends Component {
constructor(props) {
super(props);
var connection = $.hubConnection('http://[address]:[port]');
var proxy = connection.createHubProxy('[hubname]');
// atempt connection, and handle errors
connection.start()
.done(function(){ console.log('Now connected, connection ID=' + connection.id); })
.fail(function(){ console.log('Could not connect'); });
}
render() {
return (...);
}
}
export default Dashboard;
Now I get the below error from SignalR saying jQuery is not added, but I have imported it in the line above:
Error: jQuery was not found. Please ensure jQuery is referenced before
the SignalR client JavaScript file.
If I comment out import "signalr"; jQuery gets loaded correctly and i can access the $ inside the module. Why does this happen?
This is how we do it now (year 2020) with the new package #microsoft/signalr.
We use Redux, but you don't have to use Redux to be able to utilize this method.
If you are using #microsoft/signalr package instead of #aspnet/signalr, then this is how you can set it up. This is our working code in prod:
import {
JsonHubProtocol,
HubConnectionState,
HubConnectionBuilder,
LogLevel
} from '#microsoft/signalr';
const isDev = process.env.NODE_ENV === 'development';
const startSignalRConnection = async connection => {
try {
await connection.start();
console.assert(connection.state === HubConnectionState.Connected);
console.log('SignalR connection established');
} catch (err) {
console.assert(connection.state === HubConnectionState.Disconnected);
console.error('SignalR Connection Error: ', err);
setTimeout(() => startSignalRConnection(connection), 5000);
}
};
// Set up a SignalR connection to the specified hub URL, and actionEventMap.
// actionEventMap should be an object mapping event names, to eventHandlers that will
// be dispatched with the message body.
export const setupSignalRConnection = (connectionHub, actionEventMap = {}, getAccessToken) => (dispatch, getState) => {
const options = {
logMessageContent: isDev,
logger: isDev ? LogLevel.Warning : LogLevel.Error,
accessTokenFactory: () => getAccessToken(getState())
};
// create the connection instance
// withAutomaticReconnect will automatically try to reconnect
// and generate a new socket connection if needed
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withAutomaticReconnect()
.withHubProtocol(new JsonHubProtocol())
.configureLogging(LogLevel.Information)
.build();
// Note: to keep the connection open the serverTimeout should be
// larger than the KeepAlive value that is set on the server
// keepAliveIntervalInMilliseconds default is 15000 and we are using default
// serverTimeoutInMilliseconds default is 30000 and we are using 60000 set below
connection.serverTimeoutInMilliseconds = 60000;
// re-establish the connection if connection dropped
connection.onclose(error => {
console.assert(connection.state === HubConnectionState.Disconnected);
console.log('Connection closed due to error. Try refreshing this page to restart the connection', error);
});
connection.onreconnecting(error => {
console.assert(connection.state === HubConnectionState.Reconnecting);
console.log('Connection lost due to error. Reconnecting.', error);
});
connection.onreconnected(connectionId => {
console.assert(connection.state === HubConnectionState.Connected);
console.log('Connection reestablished. Connected with connectionId', connectionId);
});
startSignalRConnection(connection);
connection.on('OnEvent', res => {
const eventHandler = actionEventMap[res.eventType];
eventHandler && dispatch(eventHandler(res));
});
return connection;
};
Then you would call like the following. Please note that this a pseudo code. You may have to call it differently depending on your project setup.
import { setupSignalRConnection } from 'fileAbove.js';
const connectionHub = '/hub/service/url/events';
export const setupEventsHub = setupSignalRConnection(connectionHub, {
onMessageEvent: someMethod
}, getAccessToken);
export default () => dispatch => {
dispatch(setupEventsHub); // dispatch is coming from Redux
};
Let me know if it helped by up-voting. Thank you
UPDATE: Please note that if you are using Redux in your ReactJS app, the solution below is not necessarily the best solution. It is better to implement signalR as a middleware. You can find the best answer here.
If you are not using Redux, or you still want to implement it in a React component, then read on:
For people that are using the latest version of signalR (core v2.1), since jQuery is not a dependency of signalR any more, you can import it like:
import * as signalR from '#aspnet/signalr';
NOTE: there is now a newer version of signalr available (#microsoft/signalr) that requires a different setup. This solution only works with #aspnet/signalr. (UPDATE June 2020)
And then use it like:
signalR.HubConnectionBuilder()
Here is an example:
import React, { PureComponent } from 'react';
import { string } from 'prop-types';
import * as signalR from '#aspnet/signalr';
class SignalR extends PureComponent {
constructor (props) {
super(props);
this.connection = null;
this.onNotifReceived = this.onNotifReceived.bind(this);
}
componentDidMount () {
const protocol = new signalR.JsonHubProtocol();
const transport = signalR.HttpTransportType.WebSockets;
const options = {
transport,
logMessageContent: true,
logger: signalR.LogLevel.Trace,
accessTokenFactory: () => this.props.accessToken,
};
// create the connection instance
this.connection = new signalR.HubConnectionBuilder()
.withUrl(this.props.connectionHub, options)
.withHubProtocol(protocol)
.build();
this.connection.on('DatabaseOperation', this.onNotifReceived);
this.connection.on('DownloadSession', this.onNotifReceived);
this.connection.on('UploadSession', this.onNotifReceived);
this.connection.start()
.then(() => console.info('SignalR Connected'))
.catch(err => console.error('SignalR Connection Error: ', err));
}
componentWillUnmount () {
this.connection.stop();
}
onNotifReceived (res) {
console.info('Yayyyyy, I just received a notification!!!', res);
}
render () {
return <span />;
};
};
SignalR.propTypes = {
connectionHub: string.isRequired,
accessToken: string.isRequired
};
export default SignalR;
UPDATE: in 2020, you can use "withAutomaticReconnect()":
const connection = new HubConnectionBuilder()
.withUrl(connectionHub, options)
.withAutomaticReconnect()
.withHubProtocol(new JsonHubProtocol())
.configureLogging(LogLevel.Information)
.build();
What I figured out Signalr has dependency on jQuery. For some reason import $ from 'jquery' doesn't set window.jQuery. That's why need to do it explicitly.
I solved the issue this way:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import $ from 'jquery';
window.jQuery = $;
require('signalr');
class Dashboard extends Component {
// .....
}
export default Dashboard;
Check out SignalR no jQuery
npm i -D signalr-no-jquery
import { hubConnection } from 'signalr-no-jquery';
const connection = hubConnection('http://[address]:[port]', options);
const hubProxy = connection.createHubProxy('hubNameString');
// set up event listeners i.e. for incoming "message" event
hubProxy.on('message', function(message) {
console.log(message);
});
// connect
connection.start({ jsonp: true })
.done(function(){ console.log('Now connected, connection ID=' + connection.id); })
.fail(function(){ console.log('Could not connect'); });
https://www.npmjs.com/package/signalr-no-jquery

Setting a timer for a long period of time, i.e. multiple minutes

I want to use firebase auth with react native for Login and Signup but I got a yellow error:
Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See (https://github.com/facebook/react-native/issues/12981) for more info. (Saw setTimeout with duration 111862ms)
How Can I Fix That?
I don't want to ignore that, I want to understand this error and solve that with the best and Standard way.
And This is my Code:
export default class Login extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: '',
response: ''
}
this.signUp = this.signUp.bind(this)
this.login = this.login.bind(this)
}
async signUp() {
try {
await firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password)
this.setState({
response: 'Account Created!'
})
setTimeout(() => {
this.props.navigator.push({
id: 'App'
})
}, 1500)
} catch (error) {
this.setState({
response: error.toString()
})
}
}
async login() {
try {
await firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password)
this.setState({
response: 'user login in'
})
setTimeout(() => {
this.props.navigator.push({
id: 'App'
})
})
} catch (error) {
this.setState({
response: error.toString()
})
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.containerInputes}>
<TextInput
placeholderTextColor="gray"
placeholder="Email"
style={styles.inputText}
// onChangeText={(email) => this.setState({ email })}
onChangeText={(email) => {console.log(email);}}
/>
<TextInput
placeholderTextColor="gray"
placeholder="Password"
style={styles.inputText}
password={true}
onChangeText={(password) => this.setState({ password })}
/>
</View>
<TouchableHighlight
onPress={this.login}
style={[styles.loginButton, styles.button]}
>
<Text
style={styles.textButton}
>Login</Text>
</TouchableHighlight>
<TouchableHighlight
onPress={this.signUp}
style={[styles.loginButton, styles.button]}
>
<Text
style={styles.textButton}
>Signup</Text>
</TouchableHighlight>
</View>
)
}
}
I Reported to Google Firebase Team: (https://github.com/firebase/firebase-js-sdk/issues/97)
To fix this issue...
Navigate to your node_modules/react-native/Libraries/Core/Timers/JSTimers.js file.
Look for the variable: MAX_TIMER_DURATION_MS
Change its value to: 10000 * 1000
Save the changes (with auto format turned off) and re-build your app.
Found this answer on https://github.com/firebase/firebase-js-sdk/issues/97#issuecomment-485410026
Just add these two lines
import { LogBox } from 'react-native';
LogBox.ignoreLogs(['Setting a timer']);
Old
This fixes the yellow box and the console log. It even fixes it for Expo.
Simply place the following script at the beginning of your codebase.
import { YellowBox } from 'react-native';
import _ from 'lodash';
YellowBox.ignoreWarnings(['Setting a timer']);
const _console = _.clone(console);
console.warn = message => {
if (message.indexOf('Setting a timer') <= -1) {
_console.warn(message);
}
};
New
YellowBox is deprecated and is replaced by LogBox
import { LogBox } from 'react-native';
import _ from 'lodash';
LogBox.ignoreLogs(['Warning:...']); // ignore specific logs
LogBox.ignoreAllLogs(); // ignore all logs
const _console = _.clone(console);
console.warn = message => {
if (message.indexOf('Setting a timer') <= -1) {
_console.warn(message);
}
};
Josh Crowther Software Engineer at Google Said:
Using multi-step short duration setTimeouts doesn't actually fix the problem though. The Timer module still stays active and the app is still subject to the performance issues indicated in the warning. The issue here is that, we have use cases that require long timers, and react-native doesn't optimize for that use case.
So the net of all that is: This bug can't be fixed here we can only work around the error there are workarounds available (see Setting a timer for a long period of time, i.e. multiple minutes) that disable the warning. Doing the work to disable the warning in our code, doesn't help the issue (beyond disabling the warning), and adds additional SDK code/weight that is completely unnecessary.
I'd recommend chiming in on the issue mentioned above (i.e. facebook/react-native#12981) if you aren't comfortable w/ the workaround provided
If you are using react-native 0.63 then do the followings in your App.js file:
import the LogBox from react-native
import { LogBox } from 'react-native';
and after all the imports in your App.js file just add this line, it is not necessary to add this line in any useEffect call.
LogBox.ignoreLogs(['Setting a timer for a long period of time'])
See the docs to learn more.
OR
If you are using react-native 0.62 then do the followings in your App.js file:
import the YellowBox from react-native
import { YellowBox } from 'react-native';
and after all the imports in your App.js file just add this line, it is not necessary to add this line in any useEffect call.
YellowBox.ignoreWarnings(['Setting a timer for a long period of time']);
See the docs to learn more.
Work around issue with yellow warning 'Setting a timer' .
copy & import following file (as fast as you can ;-))
import {Platform, InteractionManager} from 'react-native';
const _setTimeout = global.setTimeout;
const _clearTimeout = global.clearTimeout;
const MAX_TIMER_DURATION_MS = 60 * 1000;
if (Platform.OS === 'android') {
// Work around issue `Setting a timer for long time`
// see: https://github.com/firebase/firebase-js-sdk/issues/97
const timerFix = {};
const runTask = (id, fn, ttl, args) => {
const waitingTime = ttl - Date.now();
if (waitingTime <= 1) {
InteractionManager.runAfterInteractions(() => {
if (!timerFix[id]) {
return;
}
delete timerFix[id];
fn(...args);
});
return;
}
const afterTime = Math.min(waitingTime, MAX_TIMER_DURATION_MS);
timerFix[id] = _setTimeout(() => runTask(id, fn, ttl, args), afterTime);
};
global.setTimeout = (fn, time, ...args) => {
if (MAX_TIMER_DURATION_MS < time) {
const ttl = Date.now() + time;
const id = '_lt_' + Object.keys(timerFix).length;
runTask(id, fn, ttl, args);
return id;
}
return _setTimeout(fn, time, ...args);
};
global.clearTimeout = id => {
if (typeof id === 'string' && id.startsWith('_lt_')) {
_clearTimeout(timerFix[id]);
delete timerFix[id];
return;
}
_clearTimeout(id);
};
}
import { YellowBox } from 'react-native';
construct() {
YellowBox.ignoreWarnings(['Setting a timer']);
}
This ignores the warning for me. You should add this to the constructor of every page that shows the warning.
Ignoring it is not the best approach, but if you're using Firebase Realtime Database. They are looking into solving this issue with their library, even though the issue is 2 years old.
What I did and it's working with me but still I don't knwo if it's a good practice or not
Navigated to file
node_modules\react-native\Libraries\Core\Timers\JSTimers.js
there is a function const MAX_TIMER_DURATION_MS = 60 * 1000 and I increased the time to be 60 * 100000 and it stopeed appearing
I got the same issue and I think it's problem of firebase web SDK so I decided to drop firebase web SDK because it runs in js thread, not react-native thread.
And I found react-native-firebase . It's better than firebase web SDK with higher performance and this issue went away
Facing the same issue.. Seems like, we'd have to hide the warning for the time being. Here's the shortest way to do it:
componentDidMount() { console.disableYellowBox = true; ... }
For firebase/firestore users: Rather than trying to mask / hide the warning, I simply reverted to the REST API. Works like a charm :)
I'm using the firebase node package for auth
I'm using regular HTTP calls for firestore (which also gets rid of the otherwise necessary atob and bto hacks)
Snippet below just uses an HTTP client (https://github.com/hardcodet/httpclient-js), you can use whatever works for you (e.g. fetch or axios).
// Firebase App (the core Firebase SDK) is always required and
// must be listed before other Firebase SDKs
import * as firebase from "firebase/app";
import "firebase/auth";
import {LoginStatus} from "./LoginStatus";
import {auth, User} from "firebase";
import {ApiResponse, DelegateBearerAuthClient, HttpClient} from "#hardcodet/httpclient";
import {Env} from "../../Env";
export class FirebaseClient {
/**
* Creates a simple API client to use with Firestore.
* We don't want to use the JS package's implementation since it has issues with
* long-running timers - see https://github.com/firebase/firebase-js-sdk/issues/97
* #param idToken The user's ID token as retrieved through firebase auth
*/
private static getFirestoreClient(idToken: string) {
const projectId = Env.firebaseProjectId;
const baseUri = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/`
const authClient = new DelegateBearerAuthClient(async () => idToken);
return new HttpClient(baseUri, {authClient});
}
/**
* Use firebase auth for login etc. because lazy.
*/
public static async userLogin(email: string, password: string): Promise<User> {
try {
const credentials: firebase.auth.UserCredential = await auth().signInWithEmailAndPassword(email, password);
return credentials.user;
} catch (error) {
console.error(error);
return undefined;
}
}
private static resolveStatus(errorCode: string): { user: User, status: LoginStatus } {
switch (errorCode) {
case "auth/invalid-email":
return {user: undefined, status: LoginStatus.InvalidEmailAddress};
case "auth/user-not-found":
return {user: undefined, status: LoginStatus.UnknownUserId};
case "auth/wrong-password":
return {user: undefined, status: LoginStatus.WrongPassword};
case "auth/email-already-in-use":
return {user: undefined, status: LoginStatus.EmailAddressAlreadyInUse};
case "auth/weak-password":
return {user: undefined, status: LoginStatus.WeakPassword};
case "auth/user-disabled":
return {user: undefined, status: LoginStatus.UserDisabled};
case "auth/expired-action-code":
return {user: undefined, status: LoginStatus.InvalidActionCode};
default:
return {user: undefined, status: LoginStatus.Undefined};
}
}
/**
* Resolve the user's keys from the backend.
*/
public static async getSomeUserData(firebaseUserId: string, idToken: string): Promise<...> {
const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
// userData here is the name of my collection in firestore. i'm using the user ID as the document ID
var result = await client.getAs<any>(`userData/${firebaseUserId}?key=${Env.firebaseApiKey}`);
if (result.success) {
const json = result.value;
const foo = json.fields.foo.stringValue;
const bar = json.fields.bar.stringValue;
return ...
} else {
if (result.notFound) {
// that document with that key doesn't exist
console.warn("no document with key " + firebaseUserId);
return undefined;
}
throw result.createError();
}
}
public static async writeSomeData(idToken: string, firebaseUserId: string, foo: string, bar: string): Promise<...> {
const data = {
"fields": {
"foo": {
"stringValue": foo
},
"bar": {
"stringValue": bar
}
}
};
// again, just do an HTTP post, use the firebase user ID as the document key
const client: HttpClient = FirebaseClient.getFirestoreClient(idToken);
const result: ApiResponse = await client.post(`userData?documentId=${firebaseUserId}&key=${Env.firebaseApiKey}`, data);
if (result.success) {
return ...
} else {
throw result.createError();
}
}
/**
* Gets the currently logged in user, if any.
*/
public static getCurrentUser(): User {
return auth().currentUser;
}
public static async sendPasswordReset(email: string): Promise<LoginStatus> {
try {
await auth().sendPasswordResetEmail(email);
return LoginStatus.Success;
} catch (error) {
return FirebaseClient.resolveStatus(error.code).status;
}
}
/**
* Clears the current user from the session.
*/
public static async signOut() {
await auth().signOut();
}
}
Note the idToken parameter - this is simply the ID token you get off the firebase User class:
const user: User = await FirebaseClient.userLogin("me#domain.com", "mypassword");
const idToken= await user.getIdToken(false);
const data = await FirebaseClient.getSomeUserData(user.uid, idToken);
In login(), your setTimeout() call is missing an interval value. As a general, browsers now do not fire timeouts/intervals if the window/tab is in the background, or at least they are not expected to be timely. This is to prevent abusive script behaviour, and to reduce power consumption by scripts that may be polling.
Your code should work in principle, if the user switches away from the window whilst the timer is running, it will complete when they return. This is probably what you want from a UX point of view, because the users sees the transition, rather than it happening in the background when they are not looking. It helps them maintain mental context.
The yellow box is because you are setting an excessively long timer according to the message (nearly two minutes) and that is unlikely to be contextually what you want. The JS environment is warning you that what you are doing it not likely what you intend. You can mute the warning if it is what you want.
I think the most close-aide solution to this problem (until RN fixed this internally) is the solution mentioned here.
// add this code to your project to reset all timeouts
const highestTimeoutId = setTimeout(() => ';');
for (let i = 0; i < highestTimeoutId; i++) {
clearTimeout(i);
}
Being used that - against Firebase call I still receive one yellow box warning (on settimeout), but any concurrent warnings never come thereafter. I'm not sure calling this at which point may not trigger the only warning also, but not any concurrent warning throws anymore after Firebase's async calls.
Another approach is to use the react-native-ignore-warnings module.
https://github.com/jamrizzi/react-native-ignore-warnings
https://www.npmjs.com/package/react-native-ignore-warnings
This even works for Expo apps.
You would use it the following way . . .
import ignoreWarnings from 'react-native-ignore-warnings';
ignoreWarnings('Setting a timer');
can run with android too..?
I'm disabling the warning with this code
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings(['Setting a timer']);

Categories