Meteor minimongo shows user logged in on client before server - javascript

It appears that if a user is logged in to a Meteor application, then loses and regains their DDP connection, there is a short moment where the client believes that it is logged in before the server does.
For example, I have a container component that updates according to the result of Meteor.loggingIn():
const MainNavigationContainer = createContainer(props => {
return {
meteorReady: Meteor.loggingIn() === false
}
}, MainNavigation);
In the MainNavigation component, I run a Meteor method which should return a result based on the user's _id (I have tried to remove irrelevant code):
class MainNavigation extends Component {
componentWillReceiveProps(nextProps) {
this.setInitialRoute(nextProps);
}
setInitialRoute = (props) => {
// Set up initial route
if (props.meteorReady) {
if (!Meteor.user()) {
this.setState({initialRoute: routes[1]});
} else {
Meteor.call('/user/events/isActive', (e, eventId) => {
if (eventId) {
// Do some stuff
} else {
// Do some other stuff
}
});
}
}
};
render() {
return (
this.props.meteorReady && this.state.initialRoute ?
<Navigator
ref={navigator => this.navigator = navigator}
initialRoute={this.state.initialRoute}
renderScene={(route, navigator) => { ... }}
/> : (
<View style={styles.container}>
<ActivityIndicator animating={true}/>
</View>
)
)
}
}
The /user/events/isActive method call should only be run if Meteor.user() is defined (which should mean the user is logged in). However, when I look at the server call:
Meteor.methods({
'/user/events/isActive': function () {
console.log('userId:', this.userId);
if (this.userId) {
const member = Members.findOne({userId: this.userId});
if (member) {
return member.eventId;
}
return false;
}
return false;
}
});
The first call of this method (after a DDP disconnect and reconnect) ends up with this.userId being equal to null.
Basically, if Meteor.user() is defined on the client, I expect this.userId to be defined on the server. However, it appears that the minimongo on the client is giving a false positive before they are actually logged in (when they disconnect and reconnect).
My question is: If Meteor.user() is defined on the client, can I safely assume that this.userId will be defined on the server? As of now, I would say I cannot, so is there any other way for me to reliably determine if the user is truly logged in from the client side?

After a lot of debugging, I have finally figured out what was going on.
The container sends new props to its child component any time a reactive computation is invalidated. Also, Meteor methods are asynchronous, and if they are not resolved on the server, the client will continue to try to get a response until a reconnect. However, the props that are passed into setInitialRoute are the nextProps from componentWillReceiveProps.
So, what was happening was that a call was being made right as the meteor server was disconnected, and the resolution of that call occurred after the reconnect. So the previous call of the meteor method was being evaluated, giving me a null for the this.userId.
To solve this, I just had to put a conditional within the callback of the meteor method to ensure that it was evaluated when the user is actually logged in (using the current props rather than the passed in nextProps):
Meteor.call('/user/events/isActive', (e, eventId) => {
if (this.props.meteorReady) {...}
});
This prevents the result from that call of the method from being evaluated on the client, and solved my problem.

Related

Commit mutation from action in another store via root? Vue2 Nuxt Vuex

This has been beating me up.
I have a vuex store, inside is a folder called "RyansBag" im using to test things.
I have two other folder, Alerts and Inventory. So the folder structure for each of these goes
store> Ryansbag/Alert/...
in my Inventory index.js file, we run a function to add an item to an inventory system.
async addInventory_Catalog({commit}, payload){
try{
const response = await this.$axios.put('Inventory/AddFromCatalogDefault', null, {
params:{
originalUPC: payload.upc,
clientID: payload.clientId,
saleprice: payload.sellPrice,
cost: payload.sellPrice,
Condition: payload.condition.conditionName,
Serial: payload.serialNumber,
Notes: payload.notes,
HoldDays: payload.holdDays,
}
});
console.log(response.data.success)
commit('RyansBag/Alerts/showAlerts', 'You have added a product!', {root: true})
return response.data;
} catch (error) { alert(error); console.log(error); }
},
Here we just pass the item down, and when it's done - commit the changes to our alert which is in store > RyansBag/Alerts.
You can see I tried to call it:
commit('RyansBag/Alerts/showAlerts', 'You have added a product!', {root: true})
My understanding was to simply state the commit is coming from this store as the root state...? But Im not sure if im supposed to register the commit in /Alerts as a global item somehow. ( https://vuex.vuejs.org/guide/modules.html#accessing-global-assets-in-namespaced-modules )
EDIT:::
Edit: There was no commit request in the action. added to post. Now however I get warning to not mutate state outside of state handlers..
Below is the mutation it's requesting to reach inside the alerts.
export const mutations = {
showAlerts(state, message) {
let timeout = 0
if (state.status.showAlert) {
state.status.showAlert = false
timeout = 300
}
setTimeout(() => {
state.status.showAlert = true
state.status.message = message
}, timeout)
},
hideAlerts(state) {
state.status.showAlert = false
},
}
The fix was a) making sure I called commit in the action parameters. and b) not using setTimeout in the mutation, but instead setting a mutation to hide, and using the actions to setTime.

Change state dynamically based on the external Internet connectivity - React (offline/online)

I have a React component, that includes the availability flag of Internet connectivity. UI elements have to be dynamically changed according to state real-time. Also, functions behave differently with the changes of the flag.
My current implementation polls remote API using Axios in every second using interval and updates state accordingly. I am looking for a more granular and efficient way to do this task to remove the 1-second error of state with the minimum computational cost. Considered online if and only if device has an external Internet connection
Current implementation :
class Container extends Component {
constructor(props) {
super(props)
this.state = {
isOnline: false
};
this.webAPI = new WebAPI(); //Axios wrapper
}
componentDidMount() {
setInterval(() => {
this.webAPI.poll(success => this.setState({ isOnline: success });
}, 1000);
}
render() {
return <ChildComponent isOnline={this.state.isOnline} />;
}
}
Edited:
Looking for a solution capable of detecting external Internet connectivity. The device can connect to a LAN which doesn't have an external connection. So, it is considered offline. Considers online if and only if device has access to external Internet resources.
You can use https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event
window.addEventListener('offline', (event) => {
console.log("The network connection has been lost.");
});
and https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event
for checking when you're back online
window.addEventListener('online', (event) => {
console.log("You are now connected to the network.");
});
Method one: Using legacy browser API - Navigator.onLine
Returns the online status of the browser. The property returns a boolean value, with true meaning online and false meaning offline. The property sends updates whenever the browser's ability to connect to the network changes. The update occurs when the user follows links or when a script requests a remote page. For example, the property should return false when users click links soon after they lose internet connection.
You can add it to your component lifecycle:
Play with the code below using Chrome dev tools - switch "Online" to "Offline" under the Network tab.
class App extends React.PureComponent {
state = { online: window.navigator.onLine }
componentDidMount() {
window.addEventListener('offline', this.handleNetworkChange);
window.addEventListener('online', this.handleNetworkChange);
}
componentWillUnmount() {
window.removeEventListener('offline', this.handleNetworkChange);
window.removeEventListener('online', this.handleNetworkChange);
}
handleNetworkChange = () => {
this.setState({ online: window.navigator.onLine });
}
render() {
return (
<div>
{ this.state.online ? 'you\'re online' : 'you\'re offline' }
</div>
);
}
}
ReactDOM.render(
<App />
, document.querySelector('#app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
However, I think this isn't what you want, you wanted a real-time connection validator.
Method two: Checking internet connection by using it
The only solid confirmation you can get if the external internet connectivity is working is by using it. The question is which server you should call to minimize the cost?
There are many solutions on the internet for this, any endpoint that responds with a quick 204 status is perfect, e.g.:
calling to Google server (for it being the most battle-tested (?) )
calling its cached JQuery script endpoint (so even if the server is down, you should still be able to get the script as long as you have a connection)
try fetching an image from a stable server (e.g.: https://ssl.gstatic.com/gb/images/v1_76783e20.png + date timestamp to prevent caching)
IMO, if you are running this React app on a server, it makes the most sense to call to your own server, you can call a request to load your /favicon.ico to check the connection.
This idea (of calling your own server) has been implemented by many libraries, such as Offline, is-reachable, and is widely used across the community. You can use them if you don't want to write everything by yourself. (Personally I like the NPM package is-reachable for being simple.)
Example:
import React from 'react';
import isReachable from 'is-reachable';
const URL = 'google.com:443';
const EVERY_SECOND = 1000;
export default class App extends React.PureComponent {
_isMounted = true;
state = { online: false }
componentDidMount() {
setInterval(async () => {
const online = await isReachable(URL);
if (this._isMounted) {
this.setState({ online });
}
}, EVERY_SECOND);
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
return (
<div>
{ this.state.online ? 'you\'re online' : 'you\'re offline' }
</div>
);
}
}
I believe what you have currently is already fine, just make sure that it is calling the right endpoint.
Similar SO questions:
Detect the Internet connection is offline?
Detect network connection in React Redux app - if offline, hide component from user
https://stackoverflow.com/Questions/3181080/How-To-Detect-Online-Offline-Event-Cross-Browser
Setup a custom hook
Setup a hook with the online, offline events. then update a state and return it. This way you can use it anywhere in your app with an import. Make sure you clean up with the return function. If you don't you will add more and more event listeners each time a component using the hook mounts.
const onlineHook = () => {
const {isOnline, setOnline} = React.useState();
React.useEffect(() => {
const goOnline = function(event){
setOnline(true);
});
const goOffline = function(event){
setOnline(false);
});
window.addEventListener('offline', goOffline);
window.addEventListener('online', goOnline);
return () => {
window.removeEventListener('offline', goOffline);
window.removeEventListener('online', goOnline);
}
}, [])
return isOnline
}
To use this just import the above hook and call it like this.
const isOnline = onlineHook(); // true if online, false if not
You can create a component to share between all subcomponents
used:
import React, { useState, useEffect } from "react";
export default function NetworkChecker() {
const [networkStatus, setNetworkStatus] = useState(true)
useEffect(() => {
window.addEventListener('offline', (event) => {
setNetworkStatus(false)
});
window.addEventListener('online', (event) => {
setNetworkStatus(true)
});
return function cleanupListener() {
window.removeEventListener('online', setNetworkStatus(true))
window.removeEventListener('offline', setNetworkStatus(false))
}
},[])
if (networkStatus) {
return <div className={"alert-success"}>Online</div>
} else {
return <div className={"alert-danger"}>Offline</div>
}
}

react-http-request does not change loading state after second request

I am now using react-http-request in my React.js component to send request and process the response. The URL parameter passed is relevant to the component state such that when the state changes, the component will be re-rendered and change the component display.
This works on the first request. However, I found that the component does not return a {load: true} after the second request, and I wonder how to solve this.
I tried to call the onRequest method and set the loading state for the component, but I cannot change the loading state after the request is finished (as render function cannot change the state).
react-http-request: https://github.com/mbasso/react-http-request
My Code is like below:
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType
}
},
// ... details emitted.
render: function(){
return (<Request
url={config.url.api + "/" + this.state.queryType}
method="get"
accept="application/json"
query={{ several parameter }}
>
{
({error, result, loading}) => {
if (loading || error) {
return <Loading />
}
else {
// process the result here.
}
}
}
</Request>)
}
So, my initial recommendation would be that you use some state management library (redux, mobx, etc) but it is not necessary to get a working example of your code, so:
import fetch from 'whatwg-fetch'; // gives compatibility with older browsers
var FilmList = React.createClass({
getInitialState: function(){
return {
queryType: this.props.queryType,
content: null
}
},
componentWillMount: function() {
this.fetchContent();
},
fetchContent: function() {
const uri = config.url.api + "/" + this.state.queryType;
// You can use w/e you want here (request.js, etc), but fetch is the latest standard in the js world
fetch(uri, {
method: 'GET',
// More properties as you see fit
})
.then(response => response.json()) // might need to do this ;)
.then(response => {
this.setState({
content: response
})
})
},
// ...
render: function(){
const content = this.state.content? (
// render your content based on this.state.content
): (
<Loading />
)
return content;
}
});
Haven't tested this code, but there are some nice benefits to it:
The http request is not dependant on React, which should (in theory) be for UI components.
The fetching mechanism is decoupled, and can be re-used at any point in the component lifecycle
In my opinion easier to read, divided into smaller logical chunks
I would recommend reading the React Component Lifecycle.
In this case, I read the source code of the react-http-request, and found that there is a weakness that after accepting and sending the second request, the component failed to update the state of "loading" returns.
// starts from Line 49
value: function componentWillReceiveProps(nextProps) {
if (JSON.stringify(this.props) === JSON.stringify(nextProps)) {
return;
}
this.request.abort();
this.performRequest(nextProps);
}
Manually changed the state of loading here can help reset the loading after each request received.
I changed the source code of this lib, and sent the pull request to the repo. It's now merged into master and ejected a new release.
See: https://github.com/mbasso/react-http-request/pull/3
Thus, this problem can be solved by keeping the lib update to the release (currently it is 1.0.3).

Using async meteor.call() in meteor-react render() method

I am using Meteor and React for my app. in my app, i use account-ui, account-facebook to let user is able to sign in by using facebook account.
Now, i want to check if user is using facebook to sign-in Or regular user/pass to sign-in. I look at users collection and see that
1) if user used facebook to login, there is services.facebook in user collection
2) if user used regular user/pass to login, there is services.password in users collection
in server, i have method to return loginservice (facebook or password)
Meteor.methods({
getLoginService: function(){
let services = Meteor.user().services;
if (typeof services.facebook!=='undefined'){
return 'facebook';
}else if (typeof services.password!=='undefined'){
return 'password';
}
return 'undefined';
},
});
In client side, if will check to see if that is (facebook login) Or (User/pass login), then i can render the screen accordingly
App.Components.Dashboard = React.createClass({
render() {
var lservice='';
Meteor.call("getLoginService",function(error,service) {
if (error){
}else {
lservice = service;
}
});
if (lservice === 'facebook'){
return (
<div id="dashboard-content">
<div className="row">
<div className="col-lg-12">
<h1 className="page-header">Facebook connect</h1>
<p>put facebook information here</p>
</div>
</div>
</div>
);
}else{
return (
<div id="dashboard-content">
<div className="row">
<div className="col-lg-12">
<h1 className="page-header">Facebook connect</h1>
<p>put facebook connect button here</p>
</div>
</div>
</div>
);
}
}
});
The problem is at client side. Because Meteor.call is async so that lservice will not get the value from callback function.
I also try to find a way to put html return into callback function(error, service) but not successful.
Do you know anyway to solve this problem. Or do you have any better idea to recognize what service that user used to login (maybe a hook to account-facebook)
Start by moving your call to Meteor.call into componentWillMount so that it only runs once—just before the component is first rendered.
Code in your render method will be called each time that the state or props change and you probably only need to get the login service once.
Next, rather than storing the service string in a local variable, store it in state so that component renders each time it is updated.
getInitialState() {
return {};
},
componentWillMount() {
Meteor.call("getLoginService", (error, service) => {
if(error) {
// handle error
} else {
this.setState({ service });
}
});
},
render() {
const { service } = this.state;
if(service === 'facebook') {
// return facebook view
} else if(service === 'password') {
// return password view
} else {
// return loading view
}
}
The first time your application renders, the service probably won't be available (it'll be waiting for the request to come back), so it will render the loading view in the else clause.
However, when the callback is called (providing there are no errors), the state of the component will be updated and the component will re-render.
This time the service will be available and the correct view will be shown.

Meteor Roles package - userIsInRole always returns false

I want to have a filter on routing level, checking if the user is in a specific role.
this.route('gamePage', {
path: '/game/:slug/',
onBeforeAction: teamFilter,
waitOn: function() { return […]; },
data: function() { return Games.findOne({slug: this.params.slug}); }
});
Here is my filter:
var teamFilter = function(pause) {
if (Meteor.user()) {
Meteor.call('checkPermission', this.params.slug, Meteor.userId(), function(error, result) {
if (error) {
throwError(error.reason, error.details);
return null;
}
console.log(result); // returns always false
if (!result) {
this.render('noAccess');
pause();
}
});
}
}
In my collection:
checkPermission: function(gameSlug, userId) {
if (serverVar) { // only executed on the server
var game = Games.findOne({slug: gameSlug});
if (game) {
if (!Roles.userIsInRole(userId, game._id, ['administrator', 'team'])) {
return false;
} else {
return true;
}
}
}
}
My first problem is that Roles.userIsInRole(userId, game._id, ['administrator', 'team'] always returns false. At first, I had this code in my router.js, but then I thought that it does not work because of a missing publication/subscription, so I ensured that the code runs only on the server. I checked the database and the user is in the role.
My second problem is that I get an exception (Exception in delivering result of invoking 'checkPermission': http://localhost:3000/lib/router.js?77b3b67967715e480a1ce463f3447ec61898e7d5:14:28) at this point: this.render('noAccess'); and I don't know why.
I already read this: meteor Roles.userIsInRole() always returning false but it didn't solve my problem.
Any help would be greatly appreciated.
In teamFilter hook you call Meteor.method checkPermission which works asynchronously and OnBeforeAction expects synchronous execution ( no callbacks ). That is why you always receive false.
Another thing is that you are using Roles.userIsInRole incorrectly:
Should be:
Roles.userIsInRole(this.userId, ['view-secrets','admin'], group)
In this case I would check roles on client side:
Roles.userIsInRole(userId, ['administrator', 'team'])
Probably you are worried about security with this solution.
I don't think you should.
What is the most important is data and data is protected by publish function which should check the roles.
Please note that all templates are accessible to client.
You can add roles to the user only on server for that you can user Meteor.call({}); check here method from client to call method on server's main.js and you can check after this method call if the role is added in users collection using meteor mongo and db.users.find({}).pretty() and see if the roles array is added the user of that usedId then you can use Roles.userIsInRole() function anywhere on client to check loggedin users role.

Categories

Resources