How to use async function without Hooks in React Native - node.js

I am currently developing an React Native app that involves realtime chatting feature.
The goal is to have a socket.js that will export default a socket.
(Initializing socket this way is the best in my opinion as it can ensure that io('xxx') only run once)
I want the socket to be created with a userToken that I stored in my EncryptedStorage and this means involving async await function.
(Creating socket like this because I will need the userToken to match the socket.id and put them both into a JSON in my server side)
socket.js
import { io } from "socket.io-client";
import BASE_URL from "../screens/BASE_URL";
import EncryptedStorage from 'react-native-encrypted-storage'
async function get_token() {
return await EncryptedStorage.getItem('userToken')
}
export default socket = io(`${BASE_URL}`, {
transports: ['websocket'],
extraHeaders: {
Authorization: `Bearer ${await get_token()}`
}
})
Using this code made the Authorization header in my server side shows Bearer [object Object] instead of the real user token
(I am confident that the server side code is working fine because before I added the userToken in, it was able to receive the connection)
What I have tried
I tried turning the socket into a function, something like this:
export default async function socket(){
return io(`${BASE_URL}`, {
transports: ['websocket'],
extraHeaders: {
Authorization: `Bearer ${await get_token()}`
}
})
}
But now the socket won't even get connected to my socket.io server.
I also tried something like
export default socket = io(`${BASE_URL}`, {
transports: ['websocket'],
extraHeaders: {
Authorization: `Bearer `
}
});
get_token().then(token => {
socket.extraHeaders.Authorization += token;
});
But it just skips the get_token() function and proceed to export my socket
Will appreciate any help and/or suggestion provided. Thank you!

Related

Why in svelte-kit's load function a valid route can't be resolved when code runs on the server

In my svelte-kit application I was struggeling with this NODE error ERR_INVALID_URL but was able to fix it with a solution provided in this thread. Unfortunately a deeper explanation as of why NODE can't parse the url - which is obviously only a valid route when the code runs on the client - was ommitted.
In svelte-kit's load function I'm implicitly fetch -ing an, from nodejs' perspective, invalid url (ERR_INVALID_URL)
So what I'd love to understand is, WHY does NODE fail to resolve/parse the given url?
Prerequisits:
// in $lib/utils/http.js
export function post(endpoint, data = {}) {
return fetch(endpoint, {
method: "POST",
credentials: "include",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
}).then((r) => r.json());
}
// in routes/auth/login.js -> this endpoint can't be be found by NODE
export async function post({ locals, request }) {
// ...code here
return {
body: request.json()
}
}
Here the distinction has to be made of whether the code runs on the client or on the server:
// in routes/login.svelte
import { browser } from '$app/env';
import { post } from '$lib/utils/http.js';
export async function load() {
const { data } = someDataObject;
if (browser) { // NODE wouldn't be able to find the endpoint in question ('/auth/login'), whereas the client does
return await post(`/auth/login`, { data }).then((response) => {
// ...do something with the response
});
}
return {};
}
Thanks for any explanation that sheds some light into this.
You should refactor your load function to use the fetch provided by SvelteKit. This will allow you to use relative requests on the server, which normally requires an origin. From the docs (emphasis mine):
fetch is equivalent to the native fetch web API, with a few additional
features:
it can be used to make credentialed requests on the server, as it inherits the cookie and authorization headers for the page request
it can make relative requests on the server (ordinarily, fetch requires a URL with an origin when used in a server context)
requests for endpoints go direct to the handler function during server-side rendering, without the overhead of an HTTP call
during server-side rendering, the response will be captured and inlined into the rendered HTML
during hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request
So, get the fetch from the parameter passed to load...
export async function load({ fetch }) {
const { data } = someDataObject;
return await post(`/auth/login`, fetch, { data }).then((response) => {
// ...do something with the response
});
}
... and use it in your post function
// in $lib/utils/http.js
export function post(endpoint, fetch, data = {}) { /* rest as before */ }
A future enhancement to SvelteKit may make it so you don't have to pass fetch to your utility function, but this is what you have to do for now.

Deno Simple Static CRUD Example

It appears that the current mysql driver for Deno does not yet support password authentication. I have just finished an API in PHP and would like to see an example of the same in Deno.
This is as much as you get as far as an example from their site:
import { serve } from "https://deno.land/std#0.58.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
req.respond({ body: "Hello World\n" });
}
Where do I add JSON headers on this?
Is the router native or does it have to be something called OAK?
Is it possible you could add a static GET, POST, PUT DELETE to this example returning post.json, get.json, put.json, delete.json file contents at each respective end point?
I'm just having a hard time finding examples.
Is the router native or does it have to be something called OAK?
No, there's no built-in router. You can use Oak or other HTTP framework.
To return a file, you use Deno.open which returns a Reader, and you can pass that Reader to body property of req.respond, which accepts a Reader, string or Uint8Array.
The following example will read the file {HTTP_METHOD}.json, and return its content, setting the Content-Type header to application/json.
import { serve } from "https://deno.land/std#0.58.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
async function handleRequest(req) {
try {
const headers = new Headers({ 'Content-Type': 'application/json' });
const file = await Deno.open(`./${req.method.toLowerCase()}.json`);
await req.respond({ body: file, headers })
} catch(e) {
console.error(e);
req.respond({ body: 'Internal Server Errror', status: 500 });
}
}
for await (const req of s) {
handleRequest(req);
}
The std HTTP server is a bit low level, you probably want to use a Framework.
https://github.com/oakserver/oak
https://drash.land/docs/#/
Frameworks have plenty of examples.

How do you handle CORS in an electron app?

I'm building an electron app and need to call APIs where the API provider has not enabled CORS. The typically proposed solution is to use a reverse proxy which is trivial to do when running locally by using node and cors-anywhere like this:
let port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080;
require ('cors-anywhere').createServer ().listen (port, 'localhost');
The app can then be configured to proxy all requests through the reverse proxy on localhost:8080.
So, my questions are:
Is it possible to use node and cors-anywhere in an electron app to create a reverse proxy? I don't want to force the app to make calls to a remote server.
Is there a better or standard way of doing this in an Electron app? I'm assuming I'm not the first to run into CORS issues. :)
Just overide header before send request using webRequest.onBeforeSendHeaders
const filter = {
urls: ['*://*.google.com/*']
};
const session = electron.remote.session
session.defaultSession.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
details.requestHeaders['Origin'] = null;
details.headers['Origin'] = null;
callback({ requestHeaders: details.requestHeaders })
});
put these codes in renderer process
In my application, it wasn't sufficient to remove the Origin header (by setting it to null) in the request. The server I was passing the request to always provided the Access-Control-Allow-Origin header in the response, regardless of it the Origin header is present in the request. So the embedded instance of Chrome did not like that the ACAO header did not match its understanding of the origin.
Instead, I had to change the Origin header on the request and then restore it on the Access-Control-Allow-Origin header on the response.
app.on('ready', () => {
// Modify the origin for all requests to the following urls.
const filter = {
urls: ['http://example.com/*']
};
session.defaultSession.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
console.log(details);
details.requestHeaders['Origin'] = 'http://example.com';
callback({ requestHeaders: details.requestHeaders });
}
);
session.defaultSession.webRequest.onHeadersReceived(
filter,
(details, callback) => {
console.log(details);
details.responseHeaders['Access-Control-Allow-Origin'] = [
'capacitor-electron://-'
];
callback({ responseHeaders: details.responseHeaders });
}
);
myCapacitorApp.init();
});
Try this if you are running web apps in localhost
const filter = {
urls: ['http://example.com/*'] // Remote API URS for which you are getting CORS error
}
browserWindow.webContents.session.webRequest.onBeforeSendHeaders(
filter,
(details, callback) => {
details.requestHeaders.Origin = `http://example.com/*`
callback({ requestHeaders: details.requestHeaders })
}
)
browserWindow.webContents.session.webRequest.onHeadersReceived(
filter,
(details, callback) => {
details.responseHeaders['access-control-allow-origin'] = [
'capacitor-electron://-',
'http://localhost:3000' // URL your local electron app hosted
]
callback({ responseHeaders: details.responseHeaders })
}
)
Just had this issue today API calls with axios inside a React app bundled in Electron is returning 400
From what I can see Electron calls act as normal calls to the API urls meaning they are not affected by CORS.
Now when you wrap your calls with a CORS proxy and make a regular call to the proxy, it should error with a 400 error because it's not a CORS call.
This thread explains why cors-anywhere responds like that => https://github.com/Rob--W/cors-anywhere/issues/39
I actually removed my CORS proxies from the app before the Electron build. I still need the CORS proxy for development since I'm testing in the browser.
Hope this helps.
You can have the main process, the NodeJS server running Electron, send the request. This avoids CORS because this is a server-to-server request. You can send an event from the frontend (the render process) to the main process using IPC. In the main process you can listen to this event, send the HTTP request, and return a promise to the frontend.
In main.js (the script where the Electron window is created):
import { app, protocol, BrowserWindow, ipcMain } from ‘electron’
import axios from 'axios'
ipcMain.handle('auth', async (event, ...args) => {
console.log('main: auth', event, args) const result = await axios.post(
'https://api.com/auth',
{
username: args[0].username,
password: args[0].password,
auth_type: args[1],
},
) console.log('main: auth result', result)
console.log('main: auth result.data', result.data) return result.data
})
In your frontend JS:
import { ipcRenderer } from 'electron'
sendAuthRequestUsingIpc() {
return ipcRenderer.invoke('auth',
{
username: AuthService.username,
password: AuthService.password,
},
'password',
).then((data) => {
AuthService.AUTH_TOKEN = data['access_token']
return true
}).catch((resp) => console.warn(resp))
}
I wrote an article that goes into more depth here.
While I have struggled a while with the existing answers I will provide here the solution that finally worked for me, assuming that you are on the main process.
Here are the steps involved:
You need to have access to the session object which can be obtained by one of two ways:
A) via the global session.defaultSession which is available after the app is ready.
const { session } = require('electron');
const curSession = session.defaultSession;
B) The other method is via the session on the BrowserWindow, this assumes that the windnows is already created.
win = new BrowserWindow({});
const curSession = win.webContents.session;
Once you have the session object you set the response header to the site you are sending the request from.
For example, let's say your electron BrowserWindow is loaded from http://localhost:3000 and you are making a request to example.com, here would be some sample code:
const { app, BrowserWindow, session } = require('electron');
app.whenReady().then(_ => {
// If using method B for the session you should first construct the BrowserWindow
const filter = { urls: ['*://*.example.com/*'] };
session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
details.responseHeaders['Access-Control-Allow-Origin'] = [ 'http://localhost:3000' ];
callback({ responseHeaders: details.responseHeaders });
}
// Construct the BrowserWindow if haven't done so yet...
});
Have you tried using fetch()
Check how to use fetch to make a no-cors request here
https://developers.google.com/web/updates/2015/03/introduction-to-fetch?hl=en

Reconnect socket on user login

I work with a setup created by create-react-app and use flux for data management and the application needs to implement socket on the client side (I use socket.io for this purpose).
Currently the socket is initialised in a Socket.js file the following way:
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
let socket = io.connect(remoteUrl + '?role=user');
socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
export { socket };
It does work, however the problem is that it only tries to connect to the server once, when the site is loaded. When the user opens the site unauthenticated it does not connect and misses to reconnect, thus the connection is not established and socket events are not received
I have tried to create a class instead and react an API for reconnect on the object, like:
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
function Socket() {
this.socket = io.connect(remoteUrl + '?role=user');
this.reconnect = () => {
this.socket = io.connect(remoteUrl + '?role=user');
}
}
let socket = new Socket();
socket.socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
export { socket };
I tried to call the Socket.reconnect() method, although it did not work and connection was not established either. Any idea or alternative solution?
The way I managed to solve this if anyone face the same problem with the Socket.io API:
First, you should encapsulate your Socket into an object created by the constructor, but there is no need to create a reconnect method as the connection is present already (and the auth can be handled through emitted events I will describe below) :
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
function Socket() {
this.socket = io.connect(remoteUrl + '?role=user');
this.socket.on('statusChange', (data) => {
return SocketWorker.receiveOrderStatusChange(data);
})
};
const socket = new Socket();
export { socket };
You can import the socket anywhere within your project:
import {socket} from './Socket';
And you can call:
socket.socket.emit('joinRoleRoom','user');
socket.socket.emit('joinIdRoom', _user._id);
On the server side, you just need to handled these events as follow:
socket.on('joinRoleRoom', (role) => {
socket.join(role)
console.log('Client joined to: ' + role);
});
socket.on('joinIdRoom', (id) => {
console.log('Client joined to: ' + id);
socket.join(id)
});
The socket will join the necessary rooms based on their auth info obtained during the auth process.
The original accepted answer from sznrbrt would work, but beware it has a serious security flaw.
If you do the following an attacker could join a room by just passing the desired user_id and start to receive sensitive user information. It could be private messages between two individual.
socket.socket.emit('joinRoleRoom','user');
socket.socket.emit('joinIdRoom', user_id);
Socket.io has an option to pass extraHeaders. One can use that to pass a token from the client. The server would use the desired authentication algorithm to decrypt the token and get the user_id.
Example:
socket.js
import io from 'socket.io-client';
import { remoteUrl } from './constants/RemoteUrl';
import SocketWorker from './utilities/SocketWorker';
const socket = io.connect(remoteUrl + '?role=user');
const socketAuth = () => {
socket.io.opts.extraHeaders = {
'x-auth-token': 'SET_TOKEN',
};
socket.io.opts.transportOptions = {
polling: {
extraHeaders: {
'x-auth-token': 'SET_TOKEN',
},
},
};
socket.io.disconnect();
socket.io.open();
};
export { socket, socketAuth };
client.js
import { socket, socketAuth } from './socket';
//After user logs in
socketAuth();
server.js, using a package socketio-jwt-auth
io.use(jwtAuth.authenticate({
secret: 'SECRET',
succeedWithoutToken: true
}, (payload, done) => {
if (payload && payload.id) {
return done(null, payload.id);
}
return done();
}));

socket.io-client how to set request header when making connection

I'm trying to set a http header when socket.io client makes the connection request. Is there a way to do this?
Here is what i'm doing:
// server side
var io = socketio(server);
io.use(function (socket, next) {
// authorize using authorization header in socket.request.headers
});
// client side
var socket = io(); // i'm trying to set an authorization header in this http reqeust
Any ideas? Thanks.
You can use extraHeaders option, if you are using socket.io-client >= 1.4.
For example:
var socket = io("http://localhost", {
extraHeaders: {
Authorization: "Bearer authorization_token_here"
}
});
engine.io-client, which is a backend of socket.io-client, introduced extraHeaders support on 2015-11-28.
It seems like the client doesn't support setting headers, as not all transports allow for the setting of headers.
This post by facundoolano details a workaround to authentication that doesn't require placing the auth token in the query string.
His workaround module can be found at https://github.com/invisiblejs/socketio-auth.
Makes me wonder why on server-side, socket.io allows for the request headers to be accessed...
There's a new way to do this: https://socket.io/docs/v3/middlewares/. Look under the "Sending Credentials" section.
// client
const socket = io(server, {
transports: ['websocket', 'polling', 'flashsocket'],
auth: {
token: 'abc'
}
});
// server
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (isValidJwt(token)){
next();
}else{
next(new Error("Socket authentication error"));
}
});
async function isValidJwt(token){
jwt.verify(token, secrets.jwt, function(err, decoded) {
if (err){
console.log(err);
return false;
}else{
//console.log(decoded);
return true;
}
});
}
This following information has been deprecated since socket.io 1.0
There are two methods of authorization: global or namespace (think route). The global method is set on the server with the io.set('authorization', function (handshakeData, callback) configuration call.
The handshakeData object contains the following information:
{
headers: req.headers // <Object> the headers of the request
, time: (new Date) +'' // <String> date time of the connection
, address: socket.address() // <Object> remoteAddress and remotePort object
, xdomain: !!headers.origin // <Boolean> was it a cross domain request?
, secure: socket.secure // <Boolean> https connection
, issued: +date // <Number> EPOCH of when the handshake was created
, url: request.url // <String> the entrance path of the request
, query: data.query // <Object> the result of url.parse().query or a empty object
}
The above information and a deeper explanation is available on this documentation page.
As of version 2.0.0 / 2017-01-22 engine.io-client supports
[feature] Allow extraHeaders to be set for browser clients in XHR requests (#519)
However at this point the socket.io-client is not updated to support this functionality, so couple of days may make this saga end until that time use the following instructions: https://facundoolano.wordpress.com/2014/10/11/better-authentication-for-socket-io-no-query-strings/
"transportOptions" options can be used to send extra headers in socket.io request. I also explained that here :-
Node.js + Socket.io | Set custom headers on the server
For some reason, these request headers are only received if the socket server is also socket.io.
If I connect to a python Websockets server for example I have no luck authenticating.
The only solution that worked for me is to use a different WebSocket client, for example, ws works fine.
import WebSocket from 'ws';
const socket = new WebSocket('wss://example.com/path', {
headers: {
Authorization: 'token'
},
});
Short Answer: It's imposiburu based on spec... if you just need to pass info early... why not query parameters?
socket = io('localhost:5000', {
path: '/mySocketPath',
transports: ['websocket'],
query: {
token:'some-token-value'
}
})
See #satpal-07 in
https://github.com/socketio/socket.io-client/issues/1356#issuecomment-810023635

Resources