SSE: Receive JSON from a Flask server through EventSource API - reactjs

I have a Flask application which responds to a ReactJS application primarily with json. This is important because the Flask app is sending objects from our postgres database.
Recently I started building a "feed" for our website. How I imagine it working: React client issues a GET request to the Flask server for the "items" which will populate the feed. I want to use the EventSource API, using a SSE/stream . This should yield much more fluid page rendering from the user's perspective and allow for much more data to be sent to the client with noticeable loading time (right?).
The problem: EventSource API seems to require a content_type = 'text/event-stream'... which is not ideal for sending objects which much more naturally send as JSON. When I try to send JSON and change the content_type to 'application/json', the browser complains that EventSource was expecting 'text/event-stream'.
My question: is there anything inherent to EventSource which restricts it to 'text/event-stream' responses?
Also! CORS is involved in this process. But I don't think its in the way, because it works for the rest of my app. so this is communication between an app hosted at localhost:5000 and localhost:3000.
Some code:
Flask Server
item_feed.py
#bp.get('/items/stream')
def item_stream():
count = request.args.get("count", None)
def stream_messages_to_client(count):
i = 0
while i < int(count):
data = { "message": "This is a test object."}
# message = "data: This is a test streamable message.\n\n" # This will be received.
message = json.dumps(data) # This will not be received--and this is what I want.
i += 1
yield message
# content_type = 'text/event-stream' # This will be received.
content_type = 'application/json' # This will not be received.
response = Response(stream_with_context(
stream_messages_to_client(count)
), content_type=content_type)
return response
React JS Client
ItemFeed.js
export const ItemFeed = () => {
let [searchParams, setSearchParams] = useSearchParams();
const defaultMessages = [];
const [messages, setMessages] = useState(defaultMessages);
const [isConnectionOpen, setIsConnectionOpen] = useState(false);
const paramsString = searchParams.toString();
const url = process.env.REACT_APP_SERVER + `/items/stream?${paramsString}`;
const eventSource = new EventSource(url, { withCredentials: true })
const handleConnectionOpen = () => {
setIsConnectionOpen(true);
}
const handleStreamedData = (e) => {
// const { message } = JSON.parse(e.data); // This will not parse
const message = e.data; // This will parse, assuming 'data: {{ message }}\n\n'
(messages.length < 10)
? setMessages([...messages, message])
: eventSource.close();
}
const handleConnectionClose = () => {
setIsConnectionOpen(false);
}
eventSource.onopen = handleConnectionOpen;
eventSource.onmessage = handleStreamedData;
eventSource.onclose = handleConnectionClose;
useEffect(() => {
// do something
}, [messages]);
return (
<div className="row">
<p>this is a test</p>
{messages.map((message, index) => (
<div className="col-12" key={message}>{ message }</div>
))}
</div>
);
}

Related

React, Express, Twitter oauth1.0a is sending 215 error

Currently I'm using Twitter's OAuth 1.0a.
Since Twitter doesn't allow CORS by itself, I'm trying to send request from Express server.
Since I'm using OAuth 1.0a I'm following https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens
To make a request, it says that I need to pass below information as authorization value inside header.
oath_consumer_key
oath_nonce
oauth_signature_method
oauth_timestamp
oauth_version
oauth_token
oath_signature
then I got 215 error.
I tried to regenerate my API keys, tokens but still it throws 215 error.
Below is my code in Express:
const verifyTwitter = async(req,res) => {
const API_BASE_URL = process.env.TWITTER_BASE_URL; // https://api.twitter.com/oauth
const oauth_consumer_key = process.env.TWITTER_CONSUMER_KEY;
const oauth_consumer_key_secret = process.env.TWITTER_CONSUMER_KEY;
const oauth_token = process.env.TWITTER_ACCESS_TOKEN;
const oauth_token_secret = process.env.TWITTER_ACCESS_TOKEN_SECRET;
const ACTION = '/request_token';
let nonceLen = 32;
const oauth_nonce = crypto.randomBytes(Math.ceil(nonceLen * 3 / 4))
.toString('base64')
.slice(0, nonceLen)
.replace(/\+/g, '0')
.replace(/\//g, '0');
const method = 'POST';
const oauth_timestamp = date.toUTCString();
const oauth_signature_method = 'HMAC-SHA1'
const oauth_version = '1.0'
const param_config = `oauth_consumer_key=${oauth_consumer_key}&oauth_nonce=${oauth_nonce}&oauth_signature_method=${oauth_signature_method}&oauth_timestamp=${oauth_timestamp}&oauth_token=${oauth_token}&oauth_version=${oauth_version}`
const oauth_collection_data = `${method}&${API_BASE_URL}&${param_config}`;
const sign_key = `${oauth_consumer_key_secret}&${oauth_token_secret}`;
const oauth_signature = crypto.createHmac('sha1' , sign_key).update(oauth_collection_data).digest('base64')
// const oauth_signature = hmac(sign_key,oauth_collection_data);
const oAuthToken = await needle('post',API_BASE_URL + ACTION + `?oauth_consumer_key=${oauth_consumer_key}`,{},{
headers : {
authorization: `OAuth oauth_consumer_key=${oauth_consumer_key}, oauth_nonce=${oauth_nonce}, oauth_signature_method=${oauth_signature_method}, oauth_timestamp=${oauth_timestamp}, oauth_version=${oauth_version}, oauth_token=${oauth_token} , oauth_signature=${oauth_signature}`
}
}).then(res => {console.dir(res.body); console.log(res.headers)})
return res.status(200)
}

How do i integrate pubnub in mobile devices

i am trying to integarte pubnub(replacing socket.io withh pubnub).here below i have shown code.it is working fine for pc.but in mobile devices its not working .i am not getting error as well.any body can tel what i done wrong.
till now i have tried to replace socket with my pubnub connection
import PubNub from 'pubnub';
import { PubNubProvider, usePubNub } from 'pubnub-react';
const pubnub = new PubNub({
publishKey: 'xxxxxxxxxxxx',
subscribeKey: 'xxxxxxxxxxx'
});
//leave Table when close window
const closingCode = () => {
sendMsg("leaveTable");
return null;
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
this.receiveMsg = []
}
window.onbeforeunload = closingCode;
// Read res from service via Socket IO
// socket.on("message", receiveMsg);
socket.on("message", text => {
let params = text.split("|"); //.map(p => Base64.Decode(p)); // we are not using b64 now
let message = params.shift(); // message, eg. playerSitOut, clearTable
this.receiveMsg.push(message);
this.props.updateMessage({ message, params });
});
pubnub.addListener({ message: function (m) {
const channelName = m.channel; // Channel on which the message was published
const channelGroup = m.subscription; // Channel group or wildcard subscription match (if exists)
const pubTT = m.timetoken; // Publish timetoken
const msg = m.message; // Message payload
const publisher = m.publisher; // Message publisher/ } });
//pubnub.subscribe();
}

Can't send POST request from react method

I am creating a FB like app with Django and react. In the main page there's a feed where users can create posts and post them to the feed.
I am trying to implement the delete post functionality but I am running into some difficulties.
The logic is:
The user clicks on the delete button on a post and the Browser sends and XMLHttpeRequest to the server to delete that post.
Here's the react component:
class Feed extends React.Component {
constructor(props){
super(props);
this.state = {
profile_pic: profile_pic_url,
user: username,
posts: posts_from_server,
}
}
handleClick() {
const text = document.querySelector('#new_post_text').value;
if (text.length > 1) {
const data = {author: username, text: text}
// send that post to the server to save it
const csrftoken = Cookies.get('csrftoken');
const request = new XMLHttpRequest();
request.open('POST', '/create_new_post', true);
request.setRequestHeader('X-CSRFToken', csrftoken);
request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
request.onload = () => {
const response = JSON.parse(request.responseText)
this.setState({
posts : [{author: response.author, author_picture: profile_pic_url, text: response.text, date: response.date}, ...this.state.posts]
})
document.querySelector("#new_post_text").value = '';
console.log(response)
}
request.send(JSON.stringify(data))
}
}
deletePost(post_id, author) {
const post = document.getElementById(post_id)
post.style.animationPlayState = 'running';
setTimeout(() =>{
this.setState({
posts: this.state.posts.filter(post => post.id != post_id)
})
}, 1000)
// delete the post from the server
const data = {'post_author': author, 'id': post_id}
const csrftoken = Cookies.get('csrftoken');
const request = new XMLHttpRequest();
request.open('POST', '/delete_post', true);
request.setRequestHeader('X-CSRFToken', csrftoken);
request.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
request.onload = () => {
response = JSON.parse(request.responseText);
console.log(response)
}
request.send(JSON.stringify(data))
}
render() {
return (
<div>
<Post_generator
current_user={this.state.user}
picture={this.state.profile_pic}
onClick={() => this.handleClick()} />
{this.state.posts.map(post => <Post
onClick={() => this.deletePost(post.id, post.author)}
key={post.id}
post_id={post.id}
current_user={this.state.user}
user={post.author}
profile_pic={post.author_picture}
text={post.text}
date={post.date}
/>)}
</div>
)
}
}
this is urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='login'),
path('login', views.login_view, name='login'),
path('index', views.index, name='index'),
path('register', views.register_view, name='register'),
path('logout', views.logout_view, name='logout'),
path('profile', views.profile, name='profile'),
path('change_profile_pic', views.change_profile_pic, name='upload_profile_pic'),
path('create_new_post', views.create_new_post, name='create_new_post'),
path('<str:friend>', views.friends_profile, name='friends_profile'),
path('delete_post', views.delete_post_function, name='delete_post'),
]
and these are the views in views.py handling the two requests:
#login_required
#require_http_methods(['POST'])
def create_new_post(request):
data = json.loads(request.body.decode("utf-8"))
print(data)
new_post = Post(author=request.user, text=data['text'])
new_post.save()
data['date']= new_post.date
return JsonResponse(data)
# delete post
#login_required
#require_http_methods(['POST'])
def delete_post_function(request):
print('sdfgdfsghdsfkgjsdklfg')
response = {"response": "data arrived to server"}
return JsonResponse(response)
The thing that is giving me trouble is the deletePost() method...
When it is executed I get a 405 error in the console.
The funny thing is that the XMLHTTPRequest in the handleClick method works just fine
Ok I figured it out...if the delete_post path in urls.py is not at the end of the urlspatterns list the code works...

react js, why is this undefined

I have this array that is type Technology and type undefined and its making things very difficult. For example I am trying to compare this array to another array and it complains that it might be undefined etc. So I am wondering why is this undefined and how to prevent this.
This component is my main component that gets the data from the server and creates 2 different arrays holding all the data from each collection.
const App = ():JSX.Element => {
//Data from server recieve both collections in one get
const onResponse = (result:JSONData):void => {
// data received from Web API
//console.table(result);
setTechnologies(result.technologies);
setAll_Courses(result.all_courses);
setLoading(false);
};
//For error
const onError = (message:string):void => console.log("*** Error has occured during AJAX data transmission: " + message);
// ----------------------------------- setting state variables
const [technologies, setTechnologies] = React.useState<Technology[]>([]);
const [all_courses, setAll_Courses] = React.useState<AllCourses[]>([]);
// Loading
const [loading, setLoading] = React.useState<boolean>(false);
// Routing and history
const history:any = useHistory();
const route:string = useLocation().pathname;
React.useEffect(():void => {
getJSONData(COURSE_SCRIPT, onResponse, onError);
}, []);
And in my other component I make an array from the data that matches this _id in the collection of technologies. So I can then work in this component with that specific document, because I need to edit the data and display data etc. Everything is difficult because its undefined.
const EditTechnology = ({technologies, all_courses, visible}:ViewProps):JSX.Element => {
let { id } = useParams<{id:string}>();
let edit_Technology:(Technology | undefined) = technologies.find(item => item._id === id) ;
you can give props a default value in child components to prevent undefined errors, like this
const EditTechnology = ({technologies = [], all_courses = [], visible}:ViewProps):JSX.Element => {
let { id } = useParams<{id:string}>();
let edit_Technology:(Technology | undefined) = technologies.find(item => item._id === id);
}
Or put it in onResponse, in this way, you don’t need to add many default value to each child components
const onResponse = (result:JSONData):void => {
// data received from Web API
//console.table(result);
setTechnologies(result.technologies || []);
setAll_Courses(result.all_courses || []);
setLoading(false);
};
I think it maybe depends on method find
const bad = ['you','will','get', 'undefined', 'if', 'you', 'looking']
.find(e => e === 'something else')
const good = ['now', 'you', 'will','find', 'it']
.find(e => e === 'it')
console.log('for `something else` -', bad)
console.log('for `it` -', good)

google local home 1500ms timeout issue

I am working on a Google Home Local Fulfillment project following "Enable local fulfillment for smart home Actions" tutorial on the Google Codelabs.
The project is only focus on local control without accessing remote control.
I need to run a query on my firestore database at executeHandler before I run this.app.getDeviceManager().send(deviceCommand).
This will give me timeout errors as below.
statusType: "RESPONSE_UNAVAILABLE"
isSuccess: false
externalDebugString: "Pubsub failed with 1500ms timeout."
Is there a way I can set the timeout more than 1500ms?
The following is my code of executeHandler:
executeHandler(request: IntentFlow.ExecuteRequest):
Promise<IntentFlow.ExecuteResponse> {
// TODO: Implement local execution
console.log("EXECUTE intent: " + JSON.stringify(request, null, 2));
const command = request.inputs[0].payload.commands[0];
const execution = command.execution[0];
const response = new Execute.Response.Builder().setRequestId(request.requestId);
const promises: Array<Promise<void>> = command.devices.map(async (device) => {
console.log("Handling EXECUTE intent for device: " + JSON.stringify(device));
// Convert execution params to a string for the local device
const params = execution.params as IFireplaceParams;
const tcpMsg = 'MWIB2,02';
const payload = this.stringToHex(tcpMsg);
console.log("Sending request to the smart home device:", payload);
const firebaseDataObj = await this.queryFirebase(device.id);
console.log("firebaseDataObj:", JSON.stringify(firebaseDataObj));
// Construct a local device command over TCP
const deviceCommand = new DataFlow.TcpRequestData();
deviceCommand.requestId = request.requestId;
deviceCommand.deviceId = device.id;
deviceCommand.data = payload;
deviceCommand.port = SERVER_PORT;
deviceCommand.operation = Constants.TcpOperation.WRITE;
try {
const result = await this.app.getDeviceManager().send(deviceCommand);
console.log("Sending deviceCommand result:", JSON.stringify(result));
const state = { online: true };
response.setSuccessState(result.deviceId, Object.assign(state, params));
} catch (err) {
err.errorCode = err.errorCode || 'invalid_request';
response.setErrorState(device.id, err.errorCode);
console.error('An error occurred sending the command', err.errorCode);
}
});
return Promise.all(promises)
.then(() => {
return response.build();
})
.catch((e) => {
const err = new IntentFlow.HandlerError(request.requestId, 'invalid_request', e.message);
return Promise.reject(err);
});
}
Communicating directly with external service from the local home app is not recommended as it might not be supported across all supported execution environments:
https://developers.google.com/assistant/smarthome/concepts/local#execution-environment
The documentation recommends instead to pass additional data required for execution as part of the customData fields of the SYNC response:
https://developers.google.com/assistant/smarthome/develop/local#update_sync_response_in_the_cloud_fulfillment

Resources