Websocket connection with javascript

Mr.wolf3 years ago

Hi,
I'm so grateful for this biggest project.

The Traccar server works fine, and I'm working to consume the api in a laravel project.

Like in API-Reference everything fine. But I'm trying to show devices positions in real-time on leaflet map, in websocket documentation the only thing required is a session, I consulted the simple in GitHub traccar-web repository i tried to execute the app.js in simple but the ajax request not saving the session and in deviecs request a popup shown for credential.

I tried other javascript example and I tried using axios library, but every time stuck in the same point session not saved and if I enter credentials in the popup the websocket fail for connection.

  • This is my code :
var url = "http://xx.xx.xx.xx:8082";
var token = "eSQB2ee4PbQ7K3XNRimDmFIAUjFg7GrZ";

var ajax = function (method, url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    xhr.open(method, url, true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            callback(JSON.parse(xhr.responseText));
        }
    };
    if (method == 'POST') {
        xhr.setRequestHeader('Content-type', 'application/json');
    }
    xhr.send()
};

ajax('GET', url + '/api/server', function(server) {
    ajax('GET', url + '/api/session?token=' + token, function(user) {
        console.log('session', user);
        ajax('GET', url + '/api/devices', function(devices) {
            console.log('devices', devices);
            var socket = new WebSocket('ws' + url.substring(4) + '/api/socket');
            socket.onopen = () => {
                console.log('socket Open')
            }
            socket.onerror = (error) => {
                console.log('socket error: ', error)
            }
            socket.onclose = function (event) {
                console.log('socket closed', event);
            };
            socket.onmessage = function (event) {
                var data = JSON.parse(event.data);
                console.log('socket message : ', data)
            }
        });
    });
});
  • Here a screen shots of the requests

credential popup in devices request
This popup show when devices get fired in the ajax sequence

session request details
This is the details of session request works fine and session json received with user details and token.

devices request details
This is the request details of devices after I decline the credential popup without entering credentials

I guess here that the session not saved by the ajax session request. i tried every thing in forum and i tried using axios library but the problem is the same.

I tried entering the credential in the popup and after submit the request works and i receive the devices list but the websocket fail because of session.

I hope to receive an answer for this problem, i feel like the solution is simple but i'm not watching it

Best regards.

Anton Tananaev3 years ago

Is your code hosted on a different host? Have you configured "SameSite"?

Mr.wolf3 years ago

Yes my code in different host
exp

  • Traccar server ip : 172.100.10.10:8082
  • my host ip : 172.100.17.15
  • Traccar installed on a windows server 2016

my config file :


<entry key='server.statistics'>http://172.100.10.10</entry>
<entry key='web.enable'>true</entry>
<entry key='web.address'>172.100.10.10</entry>
<entry key='web.port'>8082</entry>
<entry key='web.console'>true</entry>
<entry key='geocoder.enable'>true</entry>
<entry key='geocoder.type'>nominatim</entry>
<entry key='geocoder.url'>https://nominatim.openstreetmap.org/reverse</entry>
<entry key='geocoder.onRequest'>true</entry>
<entry key='geocoder.ignorePositions'>true</entry>
<entry key='geocoder.reuseDistance'>20</entry>
<entry key='filter.enable'>true</entry>
<entry key='filter.zero'>true</entry>
<entry key='filter.duplicate'>true</entry>
<entry key='filter.distance'>3</entry>
<entry key='processing.remoteAddress.enable'>true</entry>
<entry key='database.registerUnknown'>true</entry>
<entry key='database.ignoreUnknown'>false</entry>
<entry key='web.sameSiteCookie'>None</entry>
<entry key='web.origin'>*</entry>
Anton Tananaev3 years ago

Looks good. There must be some other issue with the session cookie.

Mr.wolf3 years ago

i tried with Postman at first

  • get on server

next

  • get on session with token

next

  • get on devices and i recived the devices list without any problem and in header the session created is the same in the devices request not like in javascript
Mr.wolf3 years ago

Hi

i tried many type of method but every time the same point so i tried to execute the modern web-ui in separate server and the problem raised again so the problem of session still exist every time.

the only way that i found to add authentication with token like this in AsyncSocketServlet.java

package org.traccar.api;

import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.traccar.Context;
import org.traccar.api.resource.SessionResource;
import org.traccar.config.Keys;
import org.traccar.model.User;

import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.util.*;

public class AsyncSocketServlet extends JettyWebSocketServlet {
    
    private static final String KEY_TOKEN = "token";

    @Override
    public void configure(JettyWebSocketServletFactory factory) {
        factory.setIdleTimeout(Duration.ofMillis(Context.getConfig().getLong(Keys.WEB_TIMEOUT)));
        factory.setCreator((req, resp) -> {
            if (req.getSession() != null) {
                long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
                return new AsyncSocket(userId);
            } else {
                Map params = getQueryMap(req.getQueryString());
                String token = (String) params.get(KEY_TOKEN);
                if (token != null) {
                    User user = Context.getUsersManager().getUserByToken(token);
                    if (user != null) {
                        Context.getPermissionsManager().checkUserEnabled(user.getId());
                        return new AsyncSocket(user.getId());
                    }
                }
                return null;
            }
        });
    }

    public static Map<String, String> getQueryMap(String query)  
    {  
        String[] params = query.split("&");  
        Map<String, String> map = new HashMap<String, String>();  
        for (String param : params)  
        {  String [] p=param.split("=");
            String name = p[0];  
          if(p.length>1)  {String value = p[1];  
            map.put(name, value);
          }  
        }  
        return map;  
    } 
}

After a successful compilation I tried the websocket with token in URL like this :

http://xx.xx.xx.xx:8082/api/soket?token=xxxxxxxxxxxxxxxxxxxxxxxxx

and connection successful, but i don't know if this will affect the rest of app or not ?

Anton Tananaev3 years ago

If you lose session, it will affect the rest of the app.

Perfect. I managed to connect here successfully using your script..
however I am using SSL on both ends.

Mr.wolf3 years ago

Good, the only thing is that this change will only be used for the websocket not for managing devices and users or anything else, but I'm satisfied with this because I only need a real-time websocket for map integration.

but in this case it is possible to use the API to manage, although it is possible to send commands via socket... look at the console... enter traccar and send a command to a device..

Mr.wolf3 years ago

yes but i mean like in modern interface you can do everything not just websocket modern interface uses session authentication but that's ok

Prasad Raju2 years ago

Traccar-web simple/app.js working in firefox not in chrome sign in popup while call api/device. JSESSIONID Cookie value changing each url server and session. Session value not storing and not passing in subsequent request

Mr.wolf10 months ago
package org.traccar.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.jetty.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletFactory;
import org.traccar.api.resource.SessionResource;
import org.traccar.api.security.LoginResult;
import org.traccar.api.security.LoginService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.model.User;
import org.traccar.session.ConnectionManager;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.servlet.http.HttpSession;
import jakarta.ws.rs.core.Context;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.*;

@Singleton
public class AsyncSocketServlet extends JettyWebSocketServlet {

    private final Config config;
    private final ObjectMapper objectMapper;
    private final ConnectionManager connectionManager;
    private final Storage storage;
    private static final String KEY_TOKEN = "token";

    @Inject
    private LoginService loginService;

    @Inject
    public AsyncSocketServlet(
            Config config, ObjectMapper objectMapper, ConnectionManager connectionManager, Storage storage) {
        this.config = config;
        this.objectMapper = objectMapper;
        this.connectionManager = connectionManager;
        this.storage = storage;
    }

    @Override
    public void configure(JettyWebSocketServletFactory factory) {
        factory.setIdleTimeout(Duration.ofMillis(config.getLong(Keys.WEB_TIMEOUT)));
        factory.setCreator((req, resp) -> {
            if (req.getSession() != null) {
                Long userId = (Long) ((HttpSession) req.getSession()).getAttribute(SessionResource.USER_ID_KEY);
                if (userId != null) {
                    return new AsyncSocket(objectMapper, connectionManager, storage, userId);
                } else {
                    Map<String, String> params = getQueryMap(req.getQueryString());
                    String token = (String) params.get(KEY_TOKEN);
                    if (token != null) {
                        LoginResult loginResult;
                        try {
                            loginResult = loginService.login(token);
                        } catch (StorageException | GeneralSecurityException | IOException e) {
                            return null;
                        }
                        User user = loginResult.getUser();
                        if (user != null) {
                            return new AsyncSocket(objectMapper, connectionManager, storage, user.getId());
                        }
                    }
                    return null;
                }
            }
            return null;
        });
    }

    public static Map<String, String> getQueryMap(String query)  
    {  
        String[] params = query.split("&");  
        Map<String, String> map = new HashMap<String, String>();  
        for (String param : params)  
        {  String [] p=param.split("=");
            String name = p[0];  
          if(p.length>1)  {String value = p[1];  
            map.put(name, value);
          }  
        }  
        return map;  
    } 

}
Alberto Robledo8 months ago

What I don't understand is how do you get the token? (/api/session/token)?

Mr.wolf8 months ago

The token is generated from the Web interface in parameter of user, you can check this custom version for websocket external access:
https://github.com/mr-wolf-gb/traccar-custom