Socket overview

Updated at 1709804456000
EzyPlatform utilizes ezyfox-server for the socket component, which provides a comprehensive set of features similar to ezyfox server, including:
  1. Zones
  2. Plugins
  3. Apps

Some default zones correspond to files in ezyplatform:

  1. admin: socket/settings/zones/admin-zone-settings.xml - Specifically designed for apps and plugins serving administrators.
  2. chat: socket/settings/zones/chat-zone-settings.xml - Specifically designed for apps and plugins serving chat functionality.
  3. game: socket/settings/zones/game-zone-settings.xml - Specifically designed for apps and plugins serving gaming.
  4. user: socket/settings/zones/user-zone-settings.xml - Specifically designed for apps and plugins serving user-related features.

Authentication

By default, users and administrators using the socket are authenticated through the SocketLoginController class. This class relies on the access token of users and administrators for authentication. If the token is invalid and does not allow anonymous users, access will be denied.

You can also implement your custom authentication class by inheriting from the SocketLoginController class, for example:

@EzyEventHandler(USER_LOGIN)
public class UserLoginController extends SocketLoginController {

    private final SocketMessageService messageService;
    private final SocketAnonymousUserService anonymousUserService;

    public UserLoginController(
        SocketAdminService adminService,
        SocketUserService userService,
        SocketMessageService messageService,
        SocketAnonymousUserService anonymousUserService
    ) {
        super(adminService, userService);
        this.messageService = messageService;
        this.anonymousUserService = anonymousUserService;
    }

    @Override
    protected SocketUserData doOnMissingAccessToken(
        EzyPluginContext context,
        EzyUserLoginEvent event
    ) {
        SocketUserData anonymousUserData = handleAnonymousLogin(
            event
        );
        event.setUserProperty(SocketUserData.class, anonymousUserData);
        event.setUserProperty(KEY_AUTHENTICATED, true);
        return anonymousUserData;
    }

    private SocketUserData handleAnonymousLogin(
        EzyUserLoginEvent event
    ) {
        String uuid = getUuid(event);
        String password = event.getPassword();
        AnonymousUserModel anonymousUser = anonymousUserService
            .validateAnonymousUserByUuidAndPassword(uuid, password);
        event.setUsername(extractAnonymousUserUsername(anonymousUser));
        return SocketUserData.builder()
            .userId(anonymousUser.getId())
            .userUuid(anonymousUser.getUuid())
            .socketUserType(SocketUserType.ANONYMOUS)
            .build();
    }

    @Override
    protected SocketUserData handleUserLogin(
        EzyUserLoginEvent event,
        String userAccessToken
    ) {
        SocketUserData userData = super.handleUserLogin(
            event,
            userAccessToken
        );
        synchronizeUserAnonymousMessages(
            userData.getUserId(),
            event.getPassword(),
            getUuid(event)
        );
        return userData;
    }

    private void synchronizeUserAnonymousMessages(
        long userId,
        String password,
        String uuid
    ) {
        if (isBlank(uuid)) {
            return;
        }
        try {
            AnonymousUserModel anonymousUser = anonymousUserService
                .validateAnonymousUserByUuidAndPassword(uuid, password);

            if (anonymousUser.getUserId() <= 0) {
                messageService.synchronizeMessages(
                    userId,
                    anonymousUser.getId(),
                    uuid
                );
            }
        } catch (AnonymousUserWrongPasswordException e) {
            logger.info("wrong user anonymous password", e);
        }
    }

    public static String getUuid(EzyUserLoginEvent event) {
        Object data = event.getData();
        if (!(data instanceof EzyObject)) {
            return null;
        }
        EzyObject object = (EzyObject) data;
        return object.get(COOKIE_NAME_UUID, String.class);
    }

    protected String extractAnonymousUserUsername(
        AnonymousUserModel anonymousUser
    ) {
        return SocketUserType.ANONYMOUS + "#" + anonymousUser.getId();
    }
}

This UserLoginController class handles authentication for anonymous users.

Example of javascript client

You can use any ezyfox server client SDK to connect to the socket. Below is an example using the javascript client SDK:

ezychat.createSocket = function() {
    var zoneName = 'chat';
    var appName = 'ezychat';

    var socket = {};
    socket.Commands = {
        MESSAGE: 'message',
    };

    socket.onConnected = function(callback) {
        socket.connectedCallback = callback;
    };

    socket.onConnectionFailed = function(callback) {
        socket.connectionFailedCallback = callback;
    };

    socket.emitConnected = function() {
        socket.connectedCallback();
        socket.connecting = true;
    };

    socket.emitConnectionFailed = function() {
        socket.connectionFailedCallback();
    };

    socket.callbacks = {};

    socket.send = function(cmd, data) {
        if (socket.app) {
            socket.app.send(cmd, data);
        }
    };

    var connectionFailureHandler = new EzyConnectionFailureHandler();
    connectionFailureHandler.postHandle = function(event) {
        socket.emitConnectionFailed();
    };

    var disconnectionHandler = new EzyDisconnectionHandler();

    var handshakeHandler = new EzyHandshakeHandler();
    handshakeHandler.getLoginRequest = function() {
        var accessToken = ezyweb.getCookie('accessToken');
        var uuid = ezyweb.getCookie(ezychat.clientUuidKey);
        if (accessToken) {
            var data = {}
            data[ezychat.clientUuidKey] = uuid;
            data['accessToken'] = accessToken;
            return [
                zoneName,
                'username',
                ezyweb.getCookie(ezychat.clientPasswordKey),
                data,
            ];
        }
        var data = {}
        data[ezychat.clientUuidKey] = uuid;
        return [
            zoneName,
            'username',
            ezyweb.getCookie(ezychat.clientPasswordKey),
            data
        ];
    };

    var userLoginHandler = new EzyLoginSuccessHandler();
    userLoginHandler.handleLoginSuccess = function(data) {
        socket.me = this.client.me;
        socket.me.userType = data.socketUserType;
        socket.me.userUuid = data.socketUserUuid;
        socket.me.userUuidWithType = `${data.socketUserType}#${data.socketUserUuid}`;
        this.client.sendRequest(EzyCommand.APP_ACCESS, [appName]);
    };

    var appAccessHandler = new EzyAppAccessHandler();
    appAccessHandler.postHandle = function(app, data) {
        socket.app = app;
        socket.emitConnected();
    }

    var config = new EzyClientConfig();
    config.zoneName = zoneName;
    config.reconnect.maxReconnectCount = 32768;

    var clients = EzyClients.getInstance();
    var client = clients.newClient(
        config
    );
    var setup = client.setup;
    setup.addEventHandler(
        EzyEventType.CONNECTION_FAILURE,
        connectionFailureHandler
    );
    setup.addEventHandler(
        EzyEventType.DISCONNECTION,
        disconnectionHandler
    );
    setup.addDataHandler(
        EzyCommand.HANDSHAKE,
        handshakeHandler
    );
    setup.addDataHandler(
        EzyCommand.LOGIN,
        userLoginHandler
    );
    setup.addDataHandler(
        EzyCommand.APP_ACCESS,
        appAccessHandler
    );

    setupApp = setup.setupApp(
        appName
    );

    socket.on = function(cmd, callback) {
        setupApp.addDataHandler(
            cmd,
            function(app, data) {
                callback(data);
            }
        );
    };

    socket.on(
        socket.Commands.MESSAGE,
        (data) => {
            ezychat.appendMessage(data);
        }
    );

    socket.onConnected(() => {
        ezychat.addChannelIfNotExists();
        ezychat.canNotConnectServerShowed = false;
    });

    socket.onConnectionFailed(() => {
        if (ezychat.isShortToastAvailable()
            && !ezychat.canNotConnectServerShowed
        ) {
            ezychat.canNotConnectServerShowed = true;
            shortToast(
                ezyweb.messages.error,
                ezyweb.messages.cannot_connect_to_server,
                'text-danger'
            );
        }
    });

    socket.connect = function() {
        if (!socket.connecting) {
            client.connect(ezyweb.websocketUrl || 'ws://localhost:2208/ws');
        }
    };

    socket.appSend = function(uuid, targetChannelId, message) {
        socket.app.sendRequest(
            socket.Commands.MESSAGE,
            {message: message, channelId: targetChannelId},
        );
    };
    return socket;
}

ezychat.socket = ezychat.socket || ezychat.createSocket();