Retrieve messages from app like trello in real time - Slack - reactjs

I'm using slack API to retrieve messages from bot app (like trello in slack.com). I used this API https://slack.com/api/im.history. But my goal, is to get messages from that bot app in real time to my application without reloading page. I already read the RTM API docs, and also The events API. I didn't figure out how to do so. What should I do ?
Here is server/main.js :
import { Meteor } from 'meteor/meteor';
import { HTTP } from 'meteor/http';
import '../imports/api/messages.js';
Meteor.startup(() => {
Meteor.methods({
checkSlack() {
this.unblock();
try {
var result = HTTP.call('GET','https://slack.com/api/im.history', {
params: {
token: 'xxxx-xxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx',
channel: 'xxxxxxxxxx'
}
});
return result.data.messages;
} catch (error) {
// Got a network error, timeout, or HTTP error in the 400 or 500 range.
return error.message;
}
}
});
});
imports/api/messages.js:
import { Mongo } from 'meteor/mongo';
export const Messages = new Mongo.Collection('messages');
if (Meteor.isServer) {
// This code only runs on the server
Meteor.publish('messages', function messagesPublication() {
return Messages.find();
});
}
imports/ui/Message.jsx:
import React, { Component, PropTypes } from 'react';
export default class Message extends Component {
render() {
return (
<li>{this.props.message.text}</li>
);
}
}
Message.propTypes = {
message: PropTypes.object.isRequired,
};
imports/ui/App.jsx:
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Messages } from '../api/messages.js';
import Message from './Message.jsx';
const _ = require('lodash');
// App component - represents the whole app
class App extends Component {
constructor(props){
super(props);
this.state = {
messages: [],
};
this.renderMessages = this.renderMessages.bind(this);
this.getMessages = this.getMessages.bind(this);
this.saveMessages = this.saveMessages.bind(this);
}
componentDidMount() {
this.getMessages();
}
getMessages() {
const handle = this;
Meteor.call('checkSlack',function(err, response) {
if(err){
console.log('error');
}
else {
handle.setState({
messages: response,
});
}
});
};
renderMessages() {
const messages = Messages.find({}).fetch();
return messages.map((message, index) => (
<Message key={index} message={message} />
));
}
saveMessages(){
const messages = this.state.messages;
const msgs = Messages.find({}).fetch();
var addedMsgs = _.differenceBy(messages,msgs, 'ts');
_.map(addedMsgs, (message) =>
Messages.insert(message)
);
}
render() {
return (
<div className="container">
<header>
<h1>Messages List</h1>
</header>
<button onClick={this.saveMessages}>Save</button>
{this.renderMessages()}
</div>
);
}
}
App.propTypes = {
messages: PropTypes.array.isRequired,
};
export default createContainer(() => {
Meteor.subscribe('messages');
return {
messages: Messages.find({}).fetch(),
};
}, App);
client/main.jsx:
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import App from '../imports/ui/App.jsx';
Meteor.startup(() => {
render(<App />, document.getElementById('render-target'));
});
client/main.html:
<head>
<title>App</title>
</head>
<body>
<div id="render-target"></div>
</body>

If you can get the Slack events coming through from the API, to a Meteor server, simply insert them into a Mongo collection, and then set up your Meteor client to subscribe to the database, and you will have a real time feed to your UI
UPDATE
Thanks for possting your code, now I can see what's going on.
1) In your server code you are doing this:
Meteor.startup(() => {
Meteor.methods({
It probably works OK, but these are independent things. Meteor methods often lives in another file, and is just used to declare your methods.
2) You only save the messages to the collection from the UI. They need to be inserted when you get them in the server method - then your publication and subscription will work
3) Remove the call to checkSlack from componentDidMount, and put it in the server startup.
4) Your http request to slack will only retrieve the history, you need to get more sophisticated here. Read https://api.slack.com/rtm for how you can open a socket and get a real time feed

Related

I wonder if this really is the correct way to use onAuthStateChanged

Following this react-firestore-tutorial
and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.
CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.
(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)
t {code: "auth/too-many-requests", message: "We have blocked all
requests from this device due to unusual activity. Try again later.",
a: null}a:
Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7
Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?
Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged?
(src\components\Firebase\firebase.js)
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
class Firebase {
constructor() {
app.initializeApp(config);
.......
}
.....
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
user = uid => this.db.doc(`users/${uid}`);
}
export default Firebase;
This two rect-higher-order Components:
First withAuthentication:
(src\components\Session\withAuthentication.js)
import React from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
And withAuthorization:
(src\components\Session\withAuthorization.js)
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../../constants/routes';
const withAuthorization = condition => Component => {
class WithAuthorization extends React.Component {
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
},
() => this.props.history.push(ROUTES.SIGN_IN),
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Consumer>
{authUser =>
condition(authUser) ? <Component {...this.props} /> : null
}
</AuthUserContext.Consumer>
);
}
}
return compose(
withRouter,
withFirebase,
)(WithAuthorization);
};
export default withAuthorization;
This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.
Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.
The former's next parameter is a function which stores user data on localStorage
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
while the latter's next parameter redirects to a new route based on condition.
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.
Reference:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
https://reactjs.org/docs/higher-order-components.html

Fetch data from GET request

When I call my API via my web browser I get the following result:
{"statusCode": 200, "body": "\"Cheers from AWS Lambda!\""}
However, I am now struggeling to show body via axios. Do you see what I am doing wrong?
import axios from "axios";
import React, { Component } from "react";
class App extends Component {
state = {
messages: []
};
componentDidMount() {
axios
.get(
"https://12345.execute-api.eu-central-1.amazonaws.com/prod/get-data"
)
.then(response => {
const messages = response.data;
this.setState({ messages });
});
}
render() {
return (
<ul>
{this.messages}
Test
{this.state.messages.map(message => (
<li>{message}</li>
))}
</ul>
);
}
}
export default App;
A few points:
1) Change this.messages in ul of render method to this.state.messages, as this.messages is undefined.
2) A good practice while using JSX is to keep js and html code as distinguishable as possible, so the map on a list should be done outside the return statement.
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
3) For more info about CORS error and how to rectify it while using AWS lambda, refer to this article which includes a code snippet: AWS: CORS

How do I pass browser properties to react app talking to MSBOT?

We have hosted a bot on ServiceNow and would now like to pass attributes from the browser to the BOT. How can I make this happen?
This question is actually part 2 of a question I had posted & which I have already found a solution for.
Since the BOT is already logged into ServiceNow. I want to extract some elements from the background/servicenow page source and pass it to the react app as shown below. The BOT authenticates the user by email so it would act like a SSO because he is already connected to ServiceNow with the same email id. We therefore want to simply pass that value.
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
setTimeout(() => {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language,
userid: "a.b#c.d",
username: "a.b#c.d"
}
}
});
}, 1000);
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.from.role === 'bot') {
this.setState(() => ({ newMessage: true }));
}
}
return next(action);
});
You can pass the data from your page to your bot by dispatching an event listener on the page and catching the event in the web chat implementation.
In this example, I am simulating a user having logged in with a button click. The button click creates the event. When the event is registered, it is picked up by web chat which then takes the values stored in window.NOW.user and forwards that data to the bot. To help drive the point home, I am sending a message greeting the user by name while also sending the data (name and email) behind the scenes.
Hope of help!
app.js: Imports the view for display.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import WebChatView from './webChatView';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Route path="/" exact component={WebChatView} />
</div>
</Router>
);
}
}
export default App;
webChatView.js: I import the webchat component into the view and create a function that, on click (again, just to simulate someone having logged in), creates and dispatches an event.
import React, { Component } from 'react';
import WebChat from './webchat';
class WebChatView extends Component {
constructor(props) {
super(props);
this.sendToBot = this.sendToBot.bind(this);
}
render() {
return (
<div>
<div>
<WebChat id="webchat" role="main" />
</div>
<div>
<button id="loginBtn" onClick={this.sendToBot}>Login</button>
</div>
</div>
)
}
sendToBot = () => {
let sendToBotEvent = new Event('sendToBot')
window.dispatchEvent(sendToBotEvent);
window['NOW'] = {
'user': {
'name': 'John Doe',
'email': 'john.doe#somedomain.com'
}
}
}
}
export default WebChatView;
webchat.js: Lastly, is web chat, itself. I create a store and an event listener. When the event is dispatched in the window, the message/data is also dispatched to the bot. Because I'm simulating logging in as one step, I have included a setTimeout so the window.NOW.user data has a chance to save. The store is passed to <ReactWebChat> which, subsequently, sends the associated data to the bot for processing.
import React from 'react';
import ReactWebChat, { createDirectLine, createStore, } from 'botframework-webchat';
export default class WebChat extends React.Component {
constructor(props) {
super(props);
const store = window.WebChat.createStore();
window.addEventListener('sendToBot', () => {
setTimeout(() => {
store.dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'Service Now user name',
value: window.NOW.user
}
})
store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: {
text: `Hi ${window.NOW.user.name}!`
}
})
}, 300)
})
}
this.state = {
store: store
};
componentWilUnmount() {
window.removeEventListener('sendToBot', null)
}
render() {
return (
this.state.directLine ?
<ReactWebChat
directLine={this.state.directLine}
store={this.state.store}
/>
:
<div>Connecting to bot…</div>
)
}
}

ReactJS - returns empty elements in DOM

I am new to React JS and creating an app which diplays campaigns and their details. I am using Firebase as database.
I managed to display the list of campaigns and its details. When user clicks on each campaing number it links to the preview page of this campaign:
<th><Link to={"/Preview/"+cam.id} key={cam.id}>{cam.cnumber}</Link></th>
On the Preview page the id in URL is correct and console.log(camp)(After setState) gives object with all the data but it doesn't render the list in a DOM. Anyone can help with this one please?
Console.log(cam) from renderOptions() prints:
{id: "L0000000", cnumber: undefined, adnumber: undefined, weekno: undefined, title: undefined, …}
Here is my code:
import React, { Component } from 'react';
import firebase from './firebase';
import banner from './banner.jpg';
import Header from './Header';
import { key } from "firebase-key";
class Preview extends Component {
constructor(props) {
super(props);
this.state = {
camp:[],
};
this.renderOptions = this.renderOptions.bind(this);
}
componentDidMount() {
const projectId = this.props.params.key;
var itemsRef= firebase.database().ref('campaigns/'+ projectId);
itemsRef.on('value', (snapshot) => {
let camp = snapshot.val();
let newState = [];
newState.push({
id:projectId,
cnumber: projectId.cnumber,
adnumber:projectId.adnumber,
weekno:projectId.weekno,
title:projectId.title,
brand:projectId.brand,
advertiser:projectId.advertiser
});
this.setState({
camp:newState
});
console.log(camp);
});
}
renderOptions(e) {
return this.state.camp.map((cam) => {
console.log(cam);
return(
<ul key={cam.id}>
<li>{cam.cnumber}</li>
<li>{cam.weekno}</li>
<li>{cam.adnumber}</li>
<li>{cam.title}</li>
<li>{cam.brand}</li>
<li>{cam.advertiser}</li>
</ul>
);
});
}
render(){
return (
<div>
<Header />
{this.renderOptions()}
</div>
);
}
}
export default Preview;
When you are doing newState.push at itemsRef.on('value', ...), you seem to be using projectId object to fill in the informations. Shouldn't you be using the camp object that you parsed through from snapshot?

How do you update a component in React when new data arrives on a stream?

I'm using the electron-boilerplate and Kurt Weiberth's tutorials to create my first node.js native app. I was able to create the app in the tutorial and now I want to add a component that gets updated when new tweets are streamed in given a query.
To do this, I created Tweet, TweetStream, and TweetFeed components, below. This kind of works, but I keep getting an error
Warning: flattenChildren(...): Encountered two children with the same key, ###############. Child keys must be unique; when two children share a key, only the first child will be used.
There are no duplicates when I look at the state for tweets, so I'm not sure why React is encountering them. Have I put something in the wrong place? Putting the Twit stream in a Component doesn't feel right, but I'm not sure where else it could go. I'd like to be able to update the query at some point so it seems like it needs to respond to an event when the query is updated.
Tweet
import React, { Component } from 'react';
class Tweet extends Component {
render() {
return (<li>
{this.props.tweet}
</li>);
}
}
export default Tweet;
TweetStream
import React, { Component } from 'react';
import Tweet from './Tweet';
class TweetStream extends Component {
render() {
return (
<ul>
{
this.props.tweets.map((tweet) => {
return <Tweet key={tweet.id} tweet={tweet.text} />;
})
}
</ul>
);
}
}
export default TweetStream;
TweetFeed
import React, { Component } from 'react';
const express = require('express');
const Twit = require('twit');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
class TweetFeed extends Component {
handleTweet(tweet) {
this.state = {
id: tweet.id,
text: tweet.text
};
this.props.actions.addTweet(tweet);
}
render() {
const ts = this;
io.on('connection', function (socket) {
console.log('User connected. Socket id %s', socket.id);
socket.on('disconnect', function () {
console.log('User disconnected. %s. Socket id %s', socket.id);
});
});
const T = new Twit({
consumer_key: 'KEY',
consumer_secret: 'SECRET',
access_token: 'TOKEN',
access_token_secret: 'TOKEN_SECRET',
timeout_ms: 60 * 1000, // optional HTTP request timeout to apply to all requests.
});
const stream = T.stream('statuses/filter', { track: this.props.query });
stream.on('tweet', function (tweet) {
io.sockets.emit('tweet', tweet);
ts.handleTweet(tweet);
});
return (<div />);
}
}
export default TweetFeed;
Tweets Reducer
const initialTwitterState = [];
export default function reducer(state = initialTwitterState, action) {
switch (action.type) {
case 'ADD_TWEET':
return [{id: action.text.id, text: action.text.text}, ...state];
default:
return state;
}
}
These are called from a Home component
// #flow
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import React, {Component} from 'react';
import styles from './Home.css';
import TodoInput from './TodoInput';
import TweetStream from './TweetStream'
import TweetFeed from './TweetFeed'
import * as TodoActions from '../actions/todo';
import * as TwitterActions from '../actions/twitter';
class Home extends Component {
render() {
console.log(this.props)
return (
<div>
<TweetStream tweets={this.props.tweets} actions={this.props.tweet_actions}/>
<TweetFeed query={this.props.todos.query} tweets={this.props.tweets} todos={this.props.todos} actions={this.props.tweet_actions}/>
</div>
);
}
}
function mapStateToProps(state) {
return state;
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TodoActions, dispatch),
tweet_actions: bindActionCreators(TwitterActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);

Resources