commit 592074457e67bae74f287c0fc6bcc291875f31cd Author: Tomasz Półgrabia Date: Sun Sep 10 10:25:52 2017 +0200 Initial version of chat. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb3abf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ +out diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..73f5c56 --- /dev/null +++ b/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext { + springBootVersion = '1.5.6.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'org.springframework.boot' + +version = '0.0.1-SNAPSHOT' +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + + +dependencies { + compile('org.springframework.boot:spring-boot-starter-jersey') + compile('org.springframework.boot:spring-boot-starter-web') + compile('org.springframework.boot:spring-boot-starter-websocket') + compile('org.webjars:sockjs-client:0.3.4') + testCompile('org.springframework.boot:spring-boot-starter-test') +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1a958be Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..90a06ce --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4453cce --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplication.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplication.java new file mode 100644 index 0000000..008ad98 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplication.java @@ -0,0 +1,12 @@ +package pl.polgrabiat.websockets.chat.websocketschat; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebsocketsChatApplication { + + public static void main(String[] args) { + SpringApplication.run(WebsocketsChatApplication.class, args); + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/configs/WebsocketConfig.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/configs/WebsocketConfig.java new file mode 100644 index 0000000..1d0be4e --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/configs/WebsocketConfig.java @@ -0,0 +1,22 @@ +package pl.polgrabiat.websockets.chat.websocketschat.configs; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import pl.polgrabiat.websockets.chat.websocketschat.handlers.ChatWebsocketHandler; + +import javax.inject.Inject; + +@Configuration +@EnableWebSocket +public class WebsocketConfig implements WebSocketConfigurer { + + @Inject + private ChatWebsocketHandler websocketController; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(websocketController,"/chat").withSockJS(); + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelCtx.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelCtx.java new file mode 100644 index 0000000..cba0fdd --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelCtx.java @@ -0,0 +1,34 @@ +package pl.polgrabiat.websockets.chat.websocketschat.dto; + +import java.util.HashSet; +import java.util.Set; + +public class ChannelCtx { + private String name; + private Set users = new HashSet<>(); + private Set modes = new HashSet<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + + public Set getModes() { + return modes; + } + + public void setModes(Set modes) { + this.modes = modes; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelMode.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelMode.java new file mode 100644 index 0000000..d39f348 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/ChannelMode.java @@ -0,0 +1,24 @@ +package pl.polgrabiat.websockets.chat.websocketschat.dto; + +import java.util.Arrays; +import java.util.Optional; + +public enum ChannelMode { + PRIVATE("priv"), + PASSWORD_PROTECTED("pass"), + REGISTERED("reg"); + + private final String pass; + + ChannelMode(String pass) { + this.pass = pass; + } + + public String getPass() { + return pass; + } + + public static Optional fromCode(String code) { + return Arrays.stream(values()).filter(it -> it.pass.equals(code)).findFirst(); + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/GlobalCtx.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/GlobalCtx.java new file mode 100644 index 0000000..9474309 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/GlobalCtx.java @@ -0,0 +1,56 @@ +package pl.polgrabiat.websockets.chat.websocketschat.dto; + +import org.springframework.web.socket.WebSocketSession; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class GlobalCtx { + private Set sessions = new HashSet<>(); + private Map userSessions = new HashMap<>(); + private String serverName = "localhost"; + private Map channels = new HashMap<>(); + private Map userNameSessions = new HashMap<>(); + + public Set getSessions() { + return sessions; + } + + public void setSessions(Set sessions) { + this.sessions = sessions; + } + + public Map getUserSessions() { + return userSessions; + } + + public void setUserSessions(Map userSessions) { + this.userSessions = userSessions; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public Map getChannels() { + return channels; + } + + public void setChannels(Map channels) { + this.channels = channels; + } + + public Map getUserNameSessions() { + return userNameSessions; + } + + public void setUserNameSessions(Map userNameSessions) { + this.userNameSessions = userNameSessions; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/UserCtx.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/UserCtx.java new file mode 100644 index 0000000..76346a1 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/dto/UserCtx.java @@ -0,0 +1,42 @@ +package pl.polgrabiat.websockets.chat.websocketschat.dto; + +import org.springframework.web.socket.WebSocketSession; + +public class UserCtx { + private final WebSocketSession session; + private String nick; + private String userName; + private String realName; + + public UserCtx(WebSocketSession session) { + this.session = session; + } + + public String getNick() { + return nick; + } + + public void setNick(String nick) { + this.nick = nick; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getRealName() { + return realName; + } + + public void setRealName(String realName) { + this.realName = realName; + } + + public WebSocketSession getSession() { + return session; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/ChatWebsocketHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/ChatWebsocketHandler.java new file mode 100644 index 0000000..0bcdadd --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/ChatWebsocketHandler.java @@ -0,0 +1,71 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; +import pl.polgrabiat.websockets.chat.websocketschat.dto.UserCtx; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Component +public class ChatWebsocketHandler extends TextWebSocketHandler { + + private static final Logger lg = LoggerFactory.getLogger(ChatWebsocketHandler.class); + + private Set sessions = new HashSet<>(); + private Map commandHandlers = new HashMap<>(); + private GlobalCtx globalCtx = new GlobalCtx(); + + public ChatWebsocketHandler() { + commandHandlers.put("USER", new UserCommandHandler()); + commandHandlers.put("NICK", new NickCommandHandler()); + commandHandlers.put("JOIN", new JoinCommandHandler()); + commandHandlers.put("LEAVE", new LeaveCommandHandler()); + commandHandlers.put("PRIVMSG", new PrivMessageHandler()); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + super.afterConnectionEstablished(session); + sessions.add(session); + globalCtx.getUserSessions().put(session, new UserCtx(session)); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + super.afterConnectionClosed(session, status); + sessions.remove(session); + globalCtx.getUserSessions().remove(session); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + super.handleTextMessage(session, message); + + lg.info("Message received: {}", message); + String payload = message.getPayload(); + int commandEndIdx = payload.indexOf(" "); + if (commandEndIdx < 0) { + // no command + session.sendMessage(new TextMessage("Invalid command " + payload)); + return; + } + String command = payload.substring(0, commandEndIdx); + String data = payload.substring(commandEndIdx+1); + SessionCommandHandler commandHandler = commandHandlers.get(command.toUpperCase()); + if (commandHandler != null) { + commandHandler.handleCommand(globalCtx, session, payload, command, data); + return; + } + + session.sendMessage(new TextMessage("Invalid command " + payload)); + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/JoinCommandHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/JoinCommandHandler.java new file mode 100644 index 0000000..8db0163 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/JoinCommandHandler.java @@ -0,0 +1,13 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; + +import java.io.IOException; + +public class JoinCommandHandler implements SessionCommandHandler { + @Override + public boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException { + return false; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/LeaveCommandHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/LeaveCommandHandler.java new file mode 100644 index 0000000..e600fad --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/LeaveCommandHandler.java @@ -0,0 +1,13 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; + +import java.io.IOException; + +public class LeaveCommandHandler implements SessionCommandHandler { + @Override + public boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException { + return false; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/NickCommandHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/NickCommandHandler.java new file mode 100644 index 0000000..1b6236b --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/NickCommandHandler.java @@ -0,0 +1,24 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; + +import java.io.IOException; +import java.util.StringTokenizer; + +public class NickCommandHandler implements SessionCommandHandler { + @Override + public boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException { + StringTokenizer tokenizer = new StringTokenizer(data); + String nick = tokenizer.nextToken(); + + if (nick.equals(globalCtx.getUserSessions().get(session).getNick())) { + sendMessage(session,globalCtx,403, "MSG nick already used"); + return true; + } + + globalCtx.getUserSessions().get(session).setNick(nick); + + return true; + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/PrivMessageHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/PrivMessageHandler.java new file mode 100644 index 0000000..f13ceb2 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/PrivMessageHandler.java @@ -0,0 +1,58 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.apache.catalina.User; +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.ChannelCtx; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; +import pl.polgrabiat.websockets.chat.websocketschat.dto.UserCtx; + +import java.io.IOException; +import java.util.StringTokenizer; + +public class PrivMessageHandler implements SessionCommandHandler { + @Override + public boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException { + StringTokenizer tokenizer = new StringTokenizer(data); + String destination = tokenizer.nextToken(); + if (destination.startsWith("#")) { + // channel + + ChannelCtx channel = globalCtx.getChannels().get(destination); + if (channel == null) { + sendMessage(session, + globalCtx, 404, "MSG invalid destination"); + return true; + } + + UserCtx userCtx = globalCtx.getUserSessions().get(session); + + if (!channel.getUsers().contains(userCtx)) { + sendMessage(session, globalCtx, 403, "MSG You are not the member of this channel"); + return true; + } + + // sending to other users + + for (UserCtx user : channel.getUsers()) { + sendMessage(user.getSession(), globalCtx, 200, payload); + } + + return true; + + } else { + // user + + UserCtx userCtx = globalCtx.getUserSessions().get(destination); + if (userCtx == null) { + sendMessage(session, + globalCtx, 404, "MSG invalid destination"); + return true; + } + + sendMessage(userCtx.getSession(), + globalCtx, 200, payload); + + return true; + } + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/SessionCommandHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/SessionCommandHandler.java new file mode 100644 index 0000000..f5fcfcc --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/SessionCommandHandler.java @@ -0,0 +1,19 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; + +import java.io.IOException; + +public interface SessionCommandHandler { + boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException; + + default void sendMessage(WebSocketSession session, GlobalCtx globalCtx, int responseCode, String data) throws IOException { + session.sendMessage(new TextMessage(String.format(":%s %d %s %s", + globalCtx.getServerName(), + responseCode, + globalCtx.getUserSessions().get(session).getNick(), + data))); + } +} diff --git a/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/UserCommandHandler.java b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/UserCommandHandler.java new file mode 100644 index 0000000..094c947 --- /dev/null +++ b/src/main/java/pl/polgrabiat/websockets/chat/websocketschat/handlers/UserCommandHandler.java @@ -0,0 +1,39 @@ +package pl.polgrabiat.websockets.chat.websocketschat.handlers; + +import org.springframework.web.socket.WebSocketSession; +import pl.polgrabiat.websockets.chat.websocketschat.dto.GlobalCtx; +import pl.polgrabiat.websockets.chat.websocketschat.dto.UserCtx; + +import java.io.IOException; +import java.util.StringTokenizer; + +public class UserCommandHandler implements SessionCommandHandler { + @Override + public boolean handleCommand(GlobalCtx globalCtx, WebSocketSession session, String payload, String command, String data) throws IOException { + StringTokenizer tokenizer = new StringTokenizer(data," "); + String userName = tokenizer.nextToken(); + String code1 = tokenizer.nextToken(); + if (!"0".equals(code1)) { + return false; + } + + String code2 = tokenizer.nextToken(); + if (!"*".equals(code2)) { + return false; + } + + String realName = tokenizer.nextToken(":"); + UserCtx userCtx = globalCtx.getUserSessions().get(session.getId()); + + String nick = userCtx.getNick(); + if (nick == null || "".equals(nick)) { + + } + userCtx.setUserName(userName); + userCtx.setRealName(realName); + + sendMessage(session, globalCtx, 200, + "MSG OK"); + return true; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..4837ecd --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,12 @@ + + + + SockJS example + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/index.js b/src/main/resources/static/index.js new file mode 100644 index 0000000..3e4a5a3 --- /dev/null +++ b/src/main/resources/static/index.js @@ -0,0 +1,23 @@ +window.onload = function() { + var sockJs = new SockJS("/chat"); + sockJs.onopen = function() { + console.log("Socket opened"); + // TODO send a nick message + // TODO send the userName and realName message + }; + + sockJs.onclose = function() { + console.log("Socket closed"); + }; + + sockJs.onmessage = function(e) { + console.log("Received message: " + e.data); + }; + + var messageInput = document.getElementById("message"); + messageInput.onkeyup = function(e) { + if (e.keyCode == 13) { + sockJs.send(messageInput.value); + } + }; +}; \ No newline at end of file diff --git a/src/test/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplicationTests.java b/src/test/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplicationTests.java new file mode 100644 index 0000000..3d3e037 --- /dev/null +++ b/src/test/java/pl/polgrabiat/websockets/chat/websocketschat/WebsocketsChatApplicationTests.java @@ -0,0 +1,16 @@ +package pl.polgrabiat.websockets.chat.websocketschat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class WebsocketsChatApplicationTests { + + @Test + public void contextLoads() { + } + +}