I have a spring boot + angularjs application, i configured it to have a socket connection. this is my socket controller in angularjs:
function SocketController($timeout, localStorageService) {
var vm = this;
var accessToken = localStorageService.get('accessToken');
var stompClient;
// i can see valid value of token in console
console.log(accessToken)
if (accessToken) {
var socket = new SockJS('/looping',
null,
{
transports: ['xhr-streaming'],
headers: {'Authorization': 'Bearer ' + accessToken}
});
stompClient = Stomp.over(socket);
stompClient.connect({"X-Authorization": "Bearer " + accessToken}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/loops', function (message) {
console.log(message);
});
});
} else {
console.log("token expired");
}
function sendEvent(loopId, value) {
if (stompClient != null) {
stompClient.send("/topic/loops", {}, JSON.stringify({'loopId': new Date(), 'value': 'hey silver'}));
}
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
stompClient = null;
}
console.log("Disconnected");
}
}
now, the problem is that when i check request headers in firefox devtools, authorization header doesn't set as you can see below:
How can i solve this problem? I'm new to socket.
UPDATE
When i edited the request using firefox devtools and set Authorization header in request headers, received response with 200 status code but the response content in html of index.html !
Related
simple website contact submit code using fetch sending formdata to api.
the fetch method returns'Failed to execute 'fetch' on 'Window': Invalid name' error in console, meanwhile XMLHttpRequest works.
How can i fix the fetch method?
contactForm.addEventListener('submit', async function (e) {
e.preventDefault();
// console.log('clicked');
const formData = {
name: formName.value,
email: formEmail.value,
tel: formTel.value,
message: formMessage.value,
};
// FIXME
try {
const response = await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
return response.json();
} catch (err) {
console.log(err.message);
}
// XMLHttp methode works
// let xhr = new XMLHttpRequest();
// xhr.open('POST', '/');
// xhr.setRequestHeader('Content-Type', 'application/json');
// xhr.onload = function () {
// if (xhr.responseText === 'success') {
// //add a popout window
// formName.value = '';
// formEmail.value = '';
// formTel.value = '';
// message.value = '';
// } else {
// alert('Something went wrong!');
// }
// };
// xhr.send(JSON.stringify(formData));
});
I am creating an Electron app with Socket.io. When the user's computer goes into sleep mode the server disconnects from the client throwing an error "transport close". When the user tries to reconnect I check if the tokens are still valid, if they are not, I refresh them and try to send them to the socketIo server.
The problem I have is that on "reconnect_attempt" socket.io doesn't wait until I refresh the tokens to try reconnecting, it tries reconnecting right away with the old tokens, which get rejected by the server, which also seems to terminate the connection with the user impeding future reconnect attempts.
This is part of my code to connect to the server
module.exports.connect = async (JWT) => {
return new Promise( async resolve => {
console.log("connecting to the server")
const connectionOptions = {
secure: true,
query: {token: JWT},
reconnectionDelay: 4000
}
let socket = await socketIo.connect(`${process.env.SERVER_URL}:${process.env.SERVER_PORT}`, connectionOptions);
resolve(socket)
})
}
This is my code for reconnect_attempt
socket.on('reconnect_attempt', async () => {
const getCurrentJWT = require("../../main").getCurrentJWT;
let JWT = await getCurrentJWT(); //By the time this line returns, socket.io has already tried to reconnect
if(JWT.success) { //if refreshed successfully
console.log("Trying to submit new token......", JWT);
socket.query.token = JWT.JWT;
} else {
console.log("Token not refreshed.")
}
});
And this is part of what I have on the server
io.use(async (socket, next) => {
let token = socket.handshake.query.token;
//and the instruction from here https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
let tokenIsValid = await checkTokenValidity(token);
if( tokenIsValid ) {
next();
} else {
next(new Error('invalidToken'));
console.log("Not valid token")
}
})
In short, you can use auth for this.
While connecting
auth: {
token: token
}
In the time of reconnection
socket.auth.token = "NEW_TOKEN";
socket.connect();
I can share socket io implementation for this and you can modify it as your need.
For the client-side,
let unauthorized = false;
let socket = io.connect('ws://localhost:8080', {
transports: ["websocket"],
auth: {
token: GET_YOUR_TOKEN()
}
});
socket.on("connect", () => {
unauthorized = false;
});
socket.on('UNAUTHORIZED', () => {
unauthorized = true;
});
socket.on("disconnect", (reason) => {
if (reason === "io server disconnect") {
if(unauthorized) {
socket.auth.token = token;
}
socket.connect();
}
});
socket.on('PING', ()=>{
socket.emit('PONG', token);
});
For the server-side
io.on("connection", (socket) => {
socket.on('PONG', function (token) {
if (isValidToken(token) == false) {
socket.emit("UNAUTHORIZED");
socket.disconnect();
}
});
setInterval(() => {
socket.emit('PING');
}, <YOUR-TIME>);
});
Having the following in your server.
io.use( async function(socket, next) {
let address = socket.handshake.address;
run++; // 0 -> 1
// Validate Token
const token = socket.handshake.auth.token;
if(token !== undefined){
try{
await tokenVerify(token).then((payload) => {
const serverTimestamp = Math.floor(Date.now() / 1000);
const clientTimestamp = payload.exp;
if(clientTimestamp > serverTimestamp){
console.log("Connection from: " + address + " was accepted");
console.log("Token [" + token + "] from: " + address + " was accepted");
next();
}else{
console.log("Connection from: " + address + " was rejected");
console.log("Token [" + token + "] from: " + address + " was rejected");
next(new Error("unauthorized"));
}
});
}catch (e) {
console.log(e);
}
}
})
With the code above, the server will respond "unauthorized" if the token isn't valid.
So, on the client-side, we can catch that message as shown below.
socket_io.on("connect_error", (err) => {
if(err?.message === 'unauthorized'){
var timeout = (socket_reconnection_attempts === 0 ? 5000 : 60000)
console.log("Trying to reconnect in the next " + (timeout / 1000) + ' seconds')
setTimeout(function (){
console.log('Trying to reconnect manually')
socket_reconnection_attempts++;
loadAuthToken().then(function (token) {
socket_io.auth.token = token;
socket_io.connect();
})
}, timeout)
}
});
With the code above, the client-side will try to reconnect and refresh the token only if the error message is "unauthorized."
The variable "socket_reconnection_attempts" is to avoid sending a massive number of reconnection attempts in a short period of time.
I have succesfully implemented this mechanism in my application:
https://vividcode.io/Spring-5-WebFlux-with-Server-Sent-Events/
I can receive events with curl every second, as shown in the example.
My problem is: I cannot receive these events in Angular 5. I have tried many things. Currently my service code looks like this:
public getMigrationProgress(processName: string): Observable<any> {
let headers: HttpHeaders = new HttpHeaders();
headers = headers.append('X-Authorization', this._sessionService.getAuthToken());
headers = headers.append('accept', 'text/event-stream');
let url = config.restApi.url + this.getResource() + '/' + processName;
return Observable.create(observer => {
let eventSource = new EventSourcePolyfill(url, { headers: headers });
eventSource.onmessage = (event => {
observer.next(event);
this.zone.run(() => {
console.log('prpprpr');
});
});
eventSource.onopen = (event) => {
observer.next(event);
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
};
});
}
It only opens connection, does not receive events (Method onopen works once, onmessage - never). Server sends them though.
Any ideas how to fix this?
Turned out that if you set event name on server, you cannot receive it by onmessage method.
In the example the event name was set to "random". In order to receive it you have to do it like this:
eventSource.addEventListener('random', function (event) {
console.log(event);
});
I'm facing some issue whereby I sometime will get status code 401 (Unauthorised) from my phone. I'm trying to access to an API from my computer localhost (192.168.0.7).
I've a screen, when I click on a button it will navigate to a page and it will request data through API. And when I go back and navigate to same page again, it sometime will return me code 401.
So if I repeat the same step (navigate and go back) let's say 10 times. I'm getting Unauthorised like 5-7 times.
Below are my code
export function getMyCarpool(param,token) {
return dispatch => {
var requestUrl = _api + 'GetMyProduct?' + param;
fetch(requestUrl, {
method: "get",
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
})
.then((request) => {
console.log(request);
if(request.status == 200)
return request.json();
else if(request.status == 401) {
//dispatch(logout());
throw new Error('Unauthorized access.');
}
else
throw new Error('Failed to request, please try again.');
})
.then((response) => {
var message = response.message;
if(response.success == 'true')
dispatch({ message, type: GET_MY_PRODUCT_SUCCESS });
else
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
})
.catch(error => {
var message = error.message;
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
});
}
I've check the token in my phone and also trying to make many request using postman. So I don't think it's server side problem.
I'm using Laravel and using laravel passport for API authentication. I not sure why this happen if I continue to access many time, any help is greatly appreciated.
UPDATE :: I'm trying to capture whether the http request has the token from this link, and I don't get the problem anymore.
It's a healthy mechanism for token expire. Maybe you have your token (access_token) for 5 minutes, then the token expired, you should use refresh_token to regain another new token (access_token).
For code explanation:
async function fetchService(url) {
const reqSetting = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${Auth.access_token}`,
},
};
const prevRequest = { url, reqSetting };
const resp = await fetch(url, reqSetting);
if (!resp.ok) {
const error = new Error(resp.statusText || 'Request Failed!');
if (resp.status === 401 || resp.status === 400) {
const responseClone = resp.clone();
const errorInfo = await resp.json();
if (errorInfo.error == 'invalid_token') {
// console.log('Token Expired', errorInfo);
try {
await refreshToken();
const response = await fetchService(prevRequest.url);
return response;
} catch (err) {
// handle why not refresh a new token
}
}
return responseClone;
}
error.errorUrl = url;
error.code = resp.status;
throw error;
}
return resp;
}
Where the refresh token function is :
async function refreshToken() {
const url = 'https://example.com/oauth/token';
const data = {
grant_type: 'refresh_token',
refresh_token: Auth.refresh_token,
};
try {
const res = await fetch(url, data);
const data = res.json();
Auth.access_token = data.access_token;
Auth.refresh_token = data.refresh_token;
return true;
} catch (error) {
throw error;
}
}
This fetchService will automatic regain a new token if old expired and then handle old request.
PS.
If you have multiple requests same time, the fetchService will need a little optimization. You'd better choose another regain token strategy like saga.
i'm using the spring web socket , it's a chat web socket app, every user msg will be delivered to all users, this below my home.html
function connect() {
var socket = new SockJS('http://192.168.1.115:8080/ROOT/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function(greeting) {
console.log(greeting);
console.log("greetinggggggggg");
showGreeting(JSON.parse(greeting.body).content);
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function sendName() {
var name = document.getElementById('name').value;
stompClient.send("/app/hello", {}, JSON.stringify({
'name' : name
}));
}
I want to use a unity3d app as client, actually i'm using socket io to connect to a web socket endpoint.
socket io link on assets store https://www.assetstore.unity3d.com/en/#!/content/21721
i could connect to the endpoint but i could not find how to subscribe to /topic/greetings as
stompClient.subscribe('/topic/greetings', function(greeting){
console.log(greeting);
console.log("greetinggggggggg");
showGreeting(JSON.parse(greeting.body).content);
});
});
Thanks