How can I transform this websocket as promised in redux ? I wanted to use this library https://www.npmjs.com/package/websocket-as-promised , but I don't know how to implement this in my weksocket.
I need your help PLEASEEE.
This websocket for redux has been created by "exec64" and it's functional in my application but I want that it return a promise.
source: https://exec64.co.uk/blog/websockets_with_redux/
My middleware
import actions from './actions'
const socketMiddleware = (function(){
var socket = null;
const onOpen = (ws,store,token) => evt => {
//Send a handshake, or authenticate with remote end
//Tell the store we're connected
store.dispatch(actions.connected());
}
const onClose = (ws,store) => evt => {
//Tell the store we've disconnected
store.dispatch(actions.disconnected());
}
const onMessage = (ws,store) => evt => {
//Parse the JSON message received on the websocket
var msg = JSON.parse(evt.data);
switch(msg.type) {
case "CHAT_MESSAGE":
//Dispatch an action that adds the received message to our state
store.dispatch(actions.messageReceived(msg));
break;
default:
console.log("Received unknown message type: '" + msg.type + "'");
break;
}
}
return store => next => action => {
switch(action.type) {
//The user wants us to connect
case 'CONNECT':
//Start a new connection to the server
if(socket != null) {
socket.close();
}
//Send an action that shows a "connecting..." status for now
store.dispatch(actions.connecting());
//Attempt to connect (we could send a 'failed' action on error)
socket = new WebSocket(action.url);
socket.onmessage = onMessage(socket,store);
socket.onclose = onClose(socket,store);
socket.onopen = onOpen(socket,store,action.token);
break;
//The user wants us to disconnect
case 'DISCONNECT':
if(socket != null) {
socket.close();
}
socket = null;
//Set our state to disconnected
store.dispatch(actions.disconnected());
break;
//Send the 'SEND_MESSAGE' action down the websocket to the server
case 'SEND_CHAT_MESSAGE':
socket.send(JSON.stringify(action));
break;
//This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);
}
}
})();
export default socketMiddleware
My Action types
export const WS_CONNECTING = 'WS_CONNECTING'
export const WS_CONNECTED = 'WS_CONNECTED'
export const WS_DISCONNECTED = 'WS_DISCONNECTED'
export const WS_CONNECT = 'WS_CONNECT'
export const WS_DISCONNECT = 'WS_DISCONNECT
My app Actions (actions creator)
import * as types from './actionsTypes'
export function wsConnecting() {
return {
type: types.WS_CONNECTING
}
}
export function wsConnected() {
return {
type: types.WS_CONNECTED
}
}
export function wsDisconnected(reason = "No reason") {
return {
type: types.WS_DISCONNECTED,
reason
}
}
export function wsConnect(url) {
return {
type: types.WS_CONNECT,
url: url
}
}
And my reducer
import * as types from '../actions/actionsTypes'
function appReducers(state = [], action) {
switch (action.type) {
case types.WS_CONNECTING:
return Object.assign({}, state, { status: types.WS_CONNECTING });
case types.WS_CONNECTED:
return Object.assign({}, state, { status: types.WS_CONNECTED });
case types.WS_DISCONNECTED:
return Object.assign({}, state, { status: types.WS_DISCONNECTED });
default:
return state
}
}
export default appReducers
I tried to install and import the library websocket-as-promised (https://www.npmjs.com/package/websocket-as-promised), then I replaced just this code in my websocket :
socket = new WebSocket(action.url);
**By this but it not functional **
socket = new WebSocketAsPromised(action.url);
Thanks, I found the solution I wanted.
I share my custom websocket for those who want a websocket as promised with redux:
import actions from './actions'
import WebSocketAsPromised from 'websocket-as-promised';
const socketMiddleware = (function() {
var socket = null;
const onOpen = (ws, store, token) => {
//Send a handshake, or authenticate with remote end
//Tell the store we're connected
store.dispatch(actions.connected());
}
const onClose = (ws, store) => {
//Tell the store we've disconnected
store.dispatch(actions.disconnected());
}
const onMessage = (msg, store) => {
//Parse the JSON message received on the websocket
var msg = msg;
switch (msg.type) {
case "CHAT_MESSAGE":
//Dispatch an action that adds the received message to our state
store.dispatch(actions.messageReceived(msg));
break;
default:
console.log("Received unknown message type: '" + msg.type + "'");
break;
}
}
return store => next => action => {
switch (action.type) {
//The user wants us to connect
case 'CONNECT':
//Start a new connection to the server
if (socket != null) {
socket.close();
}
//Send an action that shows a "connecting..." status for now
store.dispatch(actions.connecting());
//Attempt to connect (we could send a 'failed' action on error)
socket = new WebSocketAsPromised(action.url);
socket.onMessage.addListener((data, jsonData) => onMessage(jsonData, store))
socket.onClose.addListener(() => onClose(socket, store))
socket.open()
.then(() => {onOpen(socket, store, action.token)})
break;
//The user wants us to disconnect
case 'DISCONNECT':
if (socket != null) {
socket.close();
}
socket = null;
//Set our state to disconnected
store.dispatch(actions.disconnected());
break;
//Send the 'SEND_MESSAGE' action down the websocket to the server
case 'SEND_CHAT_MESSAGE':
socket.send(JSON.stringify(action));
break;
//This action is irrelevant to us, pass it on to the next middleware
default:
return next(action);
}
}
})();
export default socketMiddleware
You can also look at redux-websocket-bridge, which can unfold all Web Socket messages into your Redux store, and relay all Redux actions into Web Socket. It would save you some work on your CHAT_MESSAGE and SEND_CHAT_MESSAGE.
On top of unfold/relay actions, it also expose Web Socket events thru as Redux actions.
Related
I am currently working on webrtc project with multiple clients (5 - 4 with input video streams and 1 for showing them) and signaling server. Main goal is to connect clients on this way:
Every client with input video stream should be connected to client that will show all of video streams. It should be connected like that because I want to use webrtc so I can have low latency. I would also like to use web sockets for signaling server. Server is written in Python and clients in Javascript. Does anyone know how to accomplish that?
Server:
from aiohttp import web
import socketio
ROOM = 'room'
sessionIDs = []
sio = socketio.AsyncServer(cors_allowed_origins='*', ping_timeout=35)
app = web.Application()
sio.attach(app)
#sio.event
async def connect(sid, environ):
print('Connected', sid)
sessionIDs.append(sid)
await sio.emit('ready', room=ROOM, skip_sid=sid)
sio.enter_room(sid, ROOM)
#sio.event
def disconnect(sid):
sio.leave_room(sid, ROOM)
print('Disconnected', sid)
#sio.event
async def data(sid, data):
print('Message from {}: {}'.format(sid, data))
await sio.emit('data', data, room=ROOM, skip_sid=sid)
if __name__ == '__main__':
web.run_app(app, port=9999)
Client:
const SIGNALING_SERVER_URL = 'http://localhost:9999';
const TURN_SERVER_URL = 'localhost:3478';
const TURN_SERVER_USERNAME = 'username';
const TURN_SERVER_CREDENTIAL = 'credential';
const PC_CONFIG = {
iceServers: [
{
urls: 'turn:' + TURN_SERVER_URL + '?transport=tcp',
username: TURN_SERVER_USERNAME,
credential: TURN_SERVER_CREDENTIAL
},
{
urls: 'turn:' + TURN_SERVER_URL + '?transport=udp',
username: TURN_SERVER_USERNAME,
credential: TURN_SERVER_CREDENTIAL
}
]
};
// Signaling methods
let socket = io(SIGNALING_SERVER_URL, { autoConnect: false }, /*{query: 'loggeduser=finalClient'}*/ /*{auth: {token: 'my-token'}}*/);
socket.on('data', (data) => {
console.log('Data received: ', data);
handleSignalingData(data);
});
socket.on('ready', () => {
console.log('Ready');
// Connection with signaling server is ready, and so is local stream
createPeerConnection();
sendOffer();
});
let sendData = (data) => {
socket.emit('data', data);
};
// WebRTC methods
let pc;
let localStream;
let remoteStreamElement = document.querySelector('#remoteStream');
let getLocalStream = () => {
socket.connect();
}
let createPeerConnection = () => {
try {
pc = new RTCPeerConnection(PC_CONFIG);
pc.onicecandidate = onIceCandidate;
pc.ontrack = onTrack;
pc.addStream(localStream);
console.log('PeerConnection created');
} catch (error) {
console.error('PeerConnection failed: ', error);
}
};
let sendOffer = () => {
console.log('Send offer');
pc.createOffer().then(
setAndSendLocalDescription,
(error) => { console.error('Send offer failed: ', error); }
);
};
let sendAnswer = () => {
console.log('Send answer');
pc.createAnswer().then(
setAndSendLocalDescription,
(error) => { console.error('Send answer failed: ', error); }
);
};
let setAndSendLocalDescription = (sessionDescription) => {
pc.setLocalDescription(sessionDescription);
console.log('Local description set');
sendData(sessionDescription);
};
let onIceCandidate = (event) => {
if (event.candidate) {
console.log('ICE candidate');
sendData({
type: 'candidate',
candidate: event.candidate
});
}
};
let onTrack = (event) => {
console.log('Add track');
remoteStreamElement.srcObject = event.streams[0];
};
let handleSignalingData = (data) => {
switch (data.type) {
case 'offer':
createPeerConnection();
pc.setRemoteDescription(new RTCSessionDescription(data));
sendAnswer();
break;
case 'answer':
pc.setRemoteDescription(new RTCSessionDescription(data));
break;
case 'candidate':
pc.addIceCandidate(new RTCIceCandidate(data.candidate));
break;
}
};
// Start connection
getLocalStream();
Currently main functionality is peer to peer connection between them, but I want to make new peer to peer connection (for every client - with input stream - connected to server) with specific client that will show all of them.
I'm developing NestJS app which asks Binance Websocket API for some data. And also created a WebSocket server that sends received data to Front. On the Back side I got all data in console.log. But on the Front I got only the first item. I can't understand what's wrong. Can you help me please?
Coin.gateway.ts
import { MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '#nestjs/websockets';
import { Server } from 'socket.io';
import { from, of, take, map, Observable } from 'rxjs';
import { Coin } from './classes/coin';
import * as coinlist from './list/coins.json'
#WebSocketGateway(811, {transports: ['websocket', 'polling'], cors: true})
export class CoinGateway {
#WebSocketServer()
server: Server;
#SubscribeMessage('events')
handleMessage(#MessageBody() data: any) {
console.log('data',data)
const coins = new Coin(coinlist, 'usdt', 'miniTicker')
return coins.getCryptoData().pipe(map((c) => {
return c
}))
}
}
Coin.ts
import { GetCryptocurrencies } from "./abstract/get-cryptocurrencies";
import { WebSocket } from "ws";
import { Logger } from "#nestjs/common";
import { Observable } from "rxjs";
export class Coin extends GetCryptocurrencies {
private readonly logger = new Logger(Coin.name)
private baseUrl: string
private url: string
constructor(coin: { name: string, symbol: string }[], pair: string, method: string) {
super(coin, pair, method)
this.baseUrl = 'wss://stream.binance.com:9443/stream?streams='
this.url = coin.map((c) => {
return `${c.symbol.toLowerCase()}${pair}#${method}`
}).join('/')
}
getCryptoData(): any {
const stream$ = new Observable((observer) => {
const ws = new WebSocket(`${this.baseUrl}${this.url}`)
ws.on('open', () => {
this.logger.log('Connection established')
})
ws.onmessage = (msg: any) => {
const message = JSON.parse(msg.data)
observer.next(message)
}
ws.on('close', () => {
this.logger.log('Connection closed')
})
})
return stream$
}
}
Client UI useEffect hook
useEffect(() => {
const socket = io('ws://localhost:811', {transports: ['websocket']})
socket.on('connect', () => {
console.log('Connection established from client')
socket.emit('events', '', (res: any) => {
console.log(res)
})
const engine = socket.io.engine;
console.log(engine.transport.name); // in most cases, prints "polling"
engine.once("upgrade", () => {
// called when the transport is upgraded (i.e. from HTTP long-polling to WebSocket)
console.log(engine.transport.name); // in most cases, prints "websocket"
});
engine.on("packetCreate", ({ type, data }) => {
// called for each packet sent
console.log('Stype', type)
console.log('Sdata', data)
});
})
}, [])
Okay after some hours of researching and I learned that I need just to return this:
return coins.getCryptoData().pipe(map((c) => {
this.server.emit('msg', c)
}))
And receive this message on Front
i using react as my front end and django as my backend , as such , it requires me to use token based authentication with django channels. The method i am employing is by sending the authentication token as a cookie header. The methodology was taken from this git hub post here
So far , i have gotten most of the working parts together , however i don't seem to be able to persist the connection , it will always return an error in my console:
ERR_CONNECTION_RESET
Here is my code:
FRONTEND: Websocket.js
class WebSocketService{
static instance = null;
callbacks = {};
static getInstance(){
if (!WebSocketService.instance){
WebSocketService.instance = new WebSocketService();
}
return WebSocketService.instance;
}
constructor(){
this.socketRef = null;
}
connect(token){
var loc = window.location
var wsStart = 'ws://'
if (loc.protocol === 'https'){
wsStart = 'wss://'
}
const path = wsStart + 'localhost:8000'+ loc.pathname
// console.log(path)
// console.log(path + "?token=" + token)
document.cookie = 'authorization=' + token + ';'
console.log(document.cookie)
this.socketRef = new WebSocket(path)
this.socketRef.onmessage = e => {
console.log('in on message')
this.socketNewMessage(e.data);
};
this.socketRef.onopen = () => {
console.log(this.props.token)
console.log("WebSocket open");
};
this.socketRef.onerror = e => {
console.log('error happ')
console.log(e.message);
};
this.socketRef.onclose = () => {
console.log("WebSocket closed, restarting..");
this.connect(token);
};
}
socketNewMessage(data){
const parsedData = JSON.parse(data);
const command = parsedData.command;
if(Object.keys(this.callbacks).length === 0){
return;
}
if(command === 'messages'){
this.callbacks[command](parsedData.messages);
}
if(command === 'new_message'){
console.log("okay so this was called")
this.callbacks[command](parsedData.message);
}
}
state(){
return this.socketRef.readyState;
}
waitForSocketConnection(callback){
const socket = this.socketRef;
const recursion = this.waitForSocketConnection;
setTimeout(
function(){
if(socket.readyState === 1){
console.log("Connection is made");
if(callback != null){
callback();
}
return;
}
else{
console.log("Wait for connection..");
recursion(callback);
}
}, 1);
}
}
let WebSocketInstance = WebSocketService.getInstance();
export default WebSocketInstance;
FRONTEND: Apps.js
class App extends Component {
componentDidMount() {
console.log('app mounting..')
this.props.onTryAutoSignup();
console.log(this.props.isAuthenticated)
if (this.props.isAuthenticated) {
WebSocketInstance.connect()
}
}
componentDidUpdate(oldProps) {
console.log('app updating props..')
if (this.props.token !== oldProps.token ) {
console.log(this.props.token)
WebSocketInstance.connect(this.props.token)
}
}
render() {
return (
<div>
<Router>
<CustomLayout {...this.props}>
<BaseRouter/>
</CustomLayout>
</Router>
</div>
);
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.token !== null ,
token : state.token
}
}
const mapDispatchToProps = dispatch => {
return {
onTryAutoSignup: () => dispatch(actions.authCheckState())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
BACKEND: token_auth.py ( A custom middleware)
#database_sync_to_async
def get_user(token_key):
try:
return Token.objects.get(key=token_key).user
except Token.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
see:
https://channels.readthedocs.io/en/latest/topics/authentication.html#custom-authentication
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
return TokenAuthMiddlewareInstance(scope, self)
class TokenAuthMiddlewareInstance:
def __init__(self, scope, middleware):
self.middleware = middleware
self.scope = dict(scope)
self.inner = self.middleware.inner
async def __call__(self, receive, send):
headers = dict(self.scope["headers"])
print(headers[b"cookie"])
if b"authorization" in headers[b"cookie"]:
print('still good here')
cookies = headers[b"cookie"].decode()
token_key = re.search("authorization=(.*)(; )?", cookies).group(1)
if token_key:
self.scope["user"] = await get_user(token_key)
return self.inner(self.scope)
BACKEND : Router.py
application = ProtocolTypeRouter({
"websocket": TokenAuthMiddlewareStack(
URLRouter([
path("", NotificationConsumer),
]),
),
})
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
Here is my the items that were printed out in my cmd prompt:
WebSocket HANDSHAKING / [127.0.0.1:59931]
b'authorization=xxxxxxxxxxxx' #<-- token from the cookie header
still good here #<--- passed first validation
user1 #<---- went into the get_user function i declared within my middleware
WebSocket DISCONNECT / [127.0.0.1:59931] #<---- disconnects...
Please help! Im lost!
the issue was with the middleware instance , it should be :
class TokenAuthMiddlewareInstance:
def __init__(self, scope, middleware):
self.middleware = middleware
self.scope = dict(scope)
self.inner = self.middleware.inner
async def __call__(self, receive, send):
close_old_connections()
headers = dict(self.scope["headers"])
print(headers[b"cookie"])
if b"authorization" in headers[b"cookie"]:
print('still good here')
cookies = headers[b"cookie"].decode()
token_key = re.search("authorization=(.*)(; )?", cookies).group(1)
if token_key:
self.scope["user"] = await get_user(token_key)
inner = self.inner(self.scope)
return await inner(receive, send)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
thanks!
I have some component which is executing some function from api file. For example :
class Modal extends React.Component {
componentDidMount() {
this.saveModel();
}
saveModel = () => {
return this.setState({loadingRequest: true}, async () => {
await Model.graphqlMutation(request).then(() => {
return this.closeAfterSave();
});
});
}
render() {
return <div>Some divs...</div>
}
}
I am using Apollo Client so I defined my client instance with links
const errorLink = onError(({networkError, graphQLErrors, operation, forward}) => {
if (graphQLErrors && graphQLErrors[0].message === 'Unauthorized' && localStorage.getItem('authToken')) {
return promiseToObservable(refreshToken()).flatMap((res) => {
return forward(operation);
});
}
});
const link = ApolloLink.from([
errorLink,
authLink,
batchHttpLink,
]);
export const client = new ApolloClient({
link,
cache: new InMemoryCache(),
defaultOptions: defaultOptions,
});
The problem: in errorLink I am handling errors but I can't pass it down to component which sent request. I would p.ex. display some message. I know React Context API and I did it, connected with my components, but I don't know how to connect to it from client.
Thanks for suggestions.
I am using the link below for implementing SignalR within react
ASP NET Core Signal R Tutorial
However, this code appears to not follow the current standards and #aspnet/signalr-client has now been marked as obselete with a message saying that #aspnet/signalr must be used
I managed to figure out that the accepted way for creating a hub connection is
// create the connection instance
var hubConnection = new signalR.HubConnectionBuilder()
.withUrl("URL", options)
.withHubProtocol(protocol)
.build();
HOwever, I dont know how to call this within react?
I tried
import signalR, {} from '#aspnet/signalr';
but that gives the error
./src/components/widgets/Chat.js
Attempted import error: '#aspnet/signalr' does not contain a default export (imported as 'signalR').
Does anyone have an updated sample for Signal R with react or know how to do this now?
The package wont install as its obselete
Paul
You can create custom middleware, you dont 'NEED' websockets per se`
This is my current application:
configureStore.js:
import * as SignalR from '#aspnet/signalr';
//to server
export default function configureStore(history, initialState) {
const middleware = [
thunk,
routerMiddleware(history),
SignalrInvokeMiddleware
];
const rootReducer = combineReducers({
...reducers,
router: connectRouter(history)
});
const enhancers = [];
const windowIfDefined = typeof window === 'undefined' ? null : window;
if (windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__) {
enhancers.push(windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__());
}
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
const connection = new SignalR.HubConnectionBuilder()
.withUrl("/notificationHub")
.configureLogging(SignalR.LogLevel.Information)
.build();
//from server
export function SignalrInvokeMiddleware(store, callback) {
return (next) => (action) => {
switch (action.type) {
case "SIGNALR_GET_CONNECTIONID":
const user = JSON.parse(localStorage.getItem('user'));
connection.invoke('getConnectionId', user.userid)
.then(conid => action.callback());
break;
case "SIGNALR_USER_JOIN_REQUEST":
let args = action.joinRequest;
connection.invoke('userJoinRequest', args.clubId, args.userId);
break;
default:
}
return next(action);
}}
export function signalrRegisterCommands(store, callback) {
connection.on('NotifyUserJoinRequest', data => {
store.dispatch({ type: 'SIGNALR_NOTIFY_USERJOIN_REQUEST', notification: data });
})
connection.start()
.then(() => {
callback();
})
.catch(err => console.error('SignalR Connection Error: ', err));
}
index.jsx:
const store = configureStore(history);
const callback = () => {
console.log('SignalR user added to group');
}
signalrRegisterCommands(store, () => {
console.log('SignalR Connected');
store.dispatch({ type: 'SIGNALR_GET_CONNECTIONID', callback });
});