Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
de2ad81f8e | |||
e4041f9e41 | |||
00a01bc81b | |||
2b3d6009f1 | |||
21fa7b5e64 | |||
5f01174764 | |||
56fd5e8c89 |
@ -1,5 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@stomp/stompjs": "^5.4.4",
|
||||
"@types/bootbox": "^5.2.0",
|
||||
"@types/datatables.net": "^1.10.19",
|
||||
"@types/datatables.net-buttons": "^1.4.3",
|
||||
@ -8,6 +9,7 @@
|
||||
"@types/jquery": "^3.3.31",
|
||||
"@types/markdown-it": "^0.0.9",
|
||||
"@types/sjcl": "^1.0.29",
|
||||
"@types/sockjs-client": "^1.1.1",
|
||||
"@types/sprintf-js": "^1.1.2",
|
||||
"alertifyjs": "^1.12.0",
|
||||
"bootbox": "^5.4.0",
|
||||
@ -24,6 +26,7 @@
|
||||
"moment": "^2.24.0",
|
||||
"promise-worker": "^2.0.1",
|
||||
"sjcl": "^1.0.8",
|
||||
"sockjs-client": "^1.5.0",
|
||||
"sprintf-js": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -56,10 +59,11 @@
|
||||
"depends": [
|
||||
"@types/jquery"
|
||||
]
|
||||
}
|
||||
},
|
||||
"sockjs-client": "global:SockJS"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "watchify src/main/frontend/chat/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/bundle.js",
|
||||
"watch:admin": "watchify src/main/frontend/admin/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/adminBundle.js"
|
||||
}
|
||||
}
|
||||
}
|
20
pom.xml
20
pom.xml
@ -15,7 +15,7 @@
|
||||
<description>A self hosted minimal E2E chat application</description>
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<gruntArg></gruntArg>
|
||||
<gruntArg />
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@ -60,11 +60,6 @@
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>com.github.ulisesbocchio</groupId>
|
||||
<artifactId>jasypt-spring-boot-starter</artifactId>
|
||||
<version>2.1.2</version>
|
||||
</dependency> -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@ -108,6 +103,11 @@
|
||||
<artifactId>Either.java</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@ -137,14 +137,6 @@
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-maven-plugin</artifactId>
|
||||
<version>5.2.4</version>
|
||||
<!-- <executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>migrate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions> -->
|
||||
<configuration>
|
||||
<driver>${spring.datasource.driverClassName}</driver>
|
||||
<url>jdbc:mysql://${chatto.datasource.url}:${chatto.datasource.port}/${chatto.datasource.database-name}</url>
|
||||
|
56
src/main/frontend/chat/component/ChatComponent.ts
Normal file
56
src/main/frontend/chat/component/ChatComponent.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { UserModel } from "../model/UserModel";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import { UserView } from "../view/UserView";
|
||||
import { ChatView } from "../view/ChatView";
|
||||
import { UserController } from "../controller/UserController";
|
||||
import { ChatController } from "../controller/ChatController";
|
||||
import { Client } from "@stomp/stompjs";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import log from "loglevel";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
|
||||
export interface Props {
|
||||
// userModel: UserModel;
|
||||
// chatModel: ChatModel;
|
||||
// userView: UserView;
|
||||
// chatView: ChatView;
|
||||
userController: UserController;
|
||||
chatController: ChatController;
|
||||
chatStompClient: Client;
|
||||
}
|
||||
|
||||
export interface State {}
|
||||
|
||||
export class ChatComponent {
|
||||
private readonly _props: Props;
|
||||
private readonly _state: State;
|
||||
|
||||
constructor(props: Props, state: State) {
|
||||
this._props = props;
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
private async wsHandler() {
|
||||
this._props.chatStompClient.onConnect = () => {
|
||||
this._props.chatStompClient.subscribe("/user/queue/reply", (reply) => {
|
||||
const data = JSON.parse(reply.body) as ChatMessageDTO;
|
||||
const plainText = this._props.userController.decryptForUser(
|
||||
data,
|
||||
data.fromUser
|
||||
);
|
||||
this._props.chatController.store(data.fromUser, <ChatMessageViewModel>{
|
||||
...data,
|
||||
message: plainText,
|
||||
messageTime: new Date(data.messageTime),
|
||||
});
|
||||
// log.debug(
|
||||
// message,
|
||||
// this._props.encryptionService.decrypt(
|
||||
// "password",
|
||||
// message.messageCipher
|
||||
// )
|
||||
// );
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
@ -1,41 +1,42 @@
|
||||
import { Controller } from "./AbstractController";
|
||||
import "../model/AbstractModel"
|
||||
import "../model/UserModel"
|
||||
import "../view/AbstractView"
|
||||
import "../view/UserView"
|
||||
import "../model/AbstractModel";
|
||||
import "../model/UserModel";
|
||||
import "../view/AbstractView";
|
||||
import "../view/UserView";
|
||||
import { Model } from "../model/AbstractModel";
|
||||
import { View } from "../view/AbstractView";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import { ChatView } from "../view/ChatView";
|
||||
import { Client } from "@stomp/stompjs";
|
||||
import { EncryptionService } from "../../common/service/EncryptionService";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import log from "loglevel";
|
||||
|
||||
export interface Props {
|
||||
model: ChatModel;
|
||||
view: ChatView;
|
||||
chatStompClient: Client;
|
||||
encryptionService: EncryptionService;
|
||||
}
|
||||
|
||||
export class ChatController {
|
||||
private _model: ChatModel;
|
||||
private _view: ChatView;
|
||||
private readonly _props: Props;
|
||||
constructor(props: Props) {
|
||||
this._props = props;
|
||||
}
|
||||
|
||||
public test(): void {
|
||||
const chatMessageViewModels: ChatMessageViewModel[] = [];
|
||||
let chatMessageViewModelMock = new ChatMessageViewModel();
|
||||
chatMessageViewModelMock.fromUser = "user1";
|
||||
chatMessageViewModelMock.toUser = "user2";
|
||||
chatMessageViewModelMock.message = "";
|
||||
chatMessageViewModelMock.messageTime = new Date();
|
||||
chatMessageViewModels.push(chatMessageViewModelMock);
|
||||
}
|
||||
|
||||
constructor(model: ChatModel, view: ChatView) {
|
||||
this._model = model;
|
||||
this._view = view;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* eventHandler
|
||||
*/
|
||||
public eventHandler(vm: ChatMessageViewModel[]): void {
|
||||
this._model.someBusinessMethod(vm);
|
||||
}
|
||||
|
||||
public test(): void {
|
||||
const chatMessageViewModels: ChatMessageViewModel[] = [];
|
||||
let chatMessageViewModelMock = new ChatMessageViewModel();
|
||||
chatMessageViewModelMock.fromUser = "user1";
|
||||
chatMessageViewModelMock.toUser = "user2";
|
||||
chatMessageViewModelMock.message = "";
|
||||
chatMessageViewModelMock.messageTime = new Date();
|
||||
chatMessageViewModels.push(chatMessageViewModelMock);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public store(userName: string, message: ChatMessageViewModel): void {
|
||||
this._props.model.storeUserMessages(userName, Array(message), "new");
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +1,30 @@
|
||||
import { Controller } from "./AbstractController";
|
||||
import "../model/AbstractModel"
|
||||
import "../model/UserModel"
|
||||
import "../view/AbstractView"
|
||||
import "../view/UserView"
|
||||
import "../model/AbstractModel";
|
||||
import "../model/UserModel";
|
||||
import "../view/AbstractView";
|
||||
import "../view/UserView";
|
||||
import { Model } from "../model/AbstractModel";
|
||||
import { View } from "../view/AbstractView";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import { UserView } from "../view/UserView";
|
||||
import { UserModel } from "../model/UserModel";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
|
||||
export class UserController {
|
||||
private _model: UserModel;
|
||||
private _view: UserView;
|
||||
private _model: UserModel;
|
||||
private _view: UserView;
|
||||
|
||||
constructor(model: UserModel, view: UserView) {
|
||||
this._model = model;
|
||||
this._view = view;
|
||||
}
|
||||
|
||||
constructor(model: UserModel, view: UserView) {
|
||||
this._model = model;
|
||||
this._view = view;
|
||||
}
|
||||
// public getActiveUsers(): void {
|
||||
// this._model.getActiveUsers();
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* eventHandler
|
||||
*/
|
||||
public eventHandler(vm: ActiveUserViewModel[]): void {
|
||||
this._model.someBusinessMethod(vm);
|
||||
}
|
||||
|
||||
public test(): void {
|
||||
const activeUsersMock: ActiveUserViewModel[] = [];
|
||||
let activeUserViewModelMock = new ActiveUserViewModel();
|
||||
activeUserViewModelMock.userName = "some user";
|
||||
// activeUserViewModelMock.lastActive = "3 hrs ago";
|
||||
activeUserViewModelMock.online = true;
|
||||
activeUsersMock.push(activeUserViewModelMock);
|
||||
activeUserViewModelMock = new ActiveUserViewModel();
|
||||
// activeUserViewModelMock.lastActive = "3 hrs ago";
|
||||
activeUserViewModelMock.online = true;
|
||||
activeUserViewModelMock.userName = "some user 2";
|
||||
activeUsersMock.push(activeUserViewModelMock);
|
||||
this.eventHandler(activeUsersMock);
|
||||
}
|
||||
|
||||
public getActiveUsers(): void {
|
||||
this._model.getActiveUsers();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
decryptForUser(dto: ChatMessageDTO, userName: string): string {
|
||||
return this._model.decryptForUser(dto, userName);
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,46 @@
|
||||
import { Client } from "@stomp/stompjs";
|
||||
import { Builder } from "builder-pattern";
|
||||
import * as Handlebars from "handlebars";
|
||||
import * as log from "loglevel";
|
||||
import { ChatController } from "./controller/ChatController";
|
||||
import { UserController } from "./controller/UserController";
|
||||
import { ChatModel } from "./model/ChatModel";
|
||||
import { ChatModelHelper } from "./model/ChatModelHelper";
|
||||
import { UserModel } from "./model/UserModel";
|
||||
import log from "loglevel";
|
||||
import moment from "moment";
|
||||
import SockJS from "sockjs-client";
|
||||
import { AlertifyNotificationService } from "../common/service/AlertifyNotificationService";
|
||||
import { EncryptionServiceFactory } from "../common/service/EncryptionServiceFactory";
|
||||
import { FuseSearchService } from "../common/service/FuseSearchService";
|
||||
import { MarkDownItMarkDownService } from "../common/service/MarkDownItMarkDownService";
|
||||
import { NotificationService } from "../common/service/NotificationService";
|
||||
import { SearchService } from "../common/service/SearchService";
|
||||
import { ChatController } from "./controller/ChatController";
|
||||
import { UserController } from "./controller/UserController";
|
||||
import { ChatModel } from "./model/ChatModel";
|
||||
import { ChatModelHelper } from "./model/ChatModelHelper";
|
||||
import { UserModel } from "./model/UserModel";
|
||||
import { TemplateFactory } from "./template/TemplateFactory";
|
||||
import { ChatView } from "./view/ChatView";
|
||||
import { ChatViewDeps } from "./view/ChatViewDeps";
|
||||
import { UserView } from "./view/UserView";
|
||||
import { UserViewDeps } from "./view/UserViewDeps";
|
||||
import { ActiveUserViewModel } from "./viewmodel/ActiveUserViewModel";
|
||||
import moment = require("moment");
|
||||
import { Credentials } from "../common/global/Credentials";
|
||||
|
||||
// log.setLevel("TRACE");
|
||||
const authToken = Credentials.authToken;
|
||||
|
||||
const chatStompClient = new Client();
|
||||
chatStompClient.webSocketFactory = () => new SockJS("/chat");
|
||||
chatStompClient.activate();
|
||||
|
||||
const pingStompClient = new Client();
|
||||
pingStompClient.webSocketFactory = () => new SockJS("/ping");
|
||||
pingStompClient.activate();
|
||||
|
||||
pingStompClient.onConnect = () => {
|
||||
pingStompClient.publish({ destination: "/app/ping" });
|
||||
pingStompClient.subscribe("/user/queue/ping", (message) => {
|
||||
log.debug(message);
|
||||
log.debug(message.body);
|
||||
});
|
||||
};
|
||||
|
||||
const usersListElement = document.getElementById("contacts-box");
|
||||
const userSearchButton = document.getElementById("user-search");
|
||||
@ -38,7 +58,11 @@ const encryptionService = EncryptionServiceFactory.getEncryptionService();
|
||||
|
||||
const chatModelHelper = new ChatModelHelper(encryptionService, ns);
|
||||
const chatModel = new ChatModel(chatModelHelper);
|
||||
const userModel = new UserModel(ns);
|
||||
const userModel = new UserModel({
|
||||
notificationService: ns,
|
||||
encryptionService,
|
||||
authToken,
|
||||
});
|
||||
const cvDeps: ChatViewDeps = {
|
||||
chatModel: chatModel,
|
||||
// @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'.
|
||||
@ -48,13 +72,19 @@ const cvDeps: ChatViewDeps = {
|
||||
),
|
||||
messageReceiveTemplate: TemplateFactory.getTemplate("msg_container_template"),
|
||||
markdownService: new MarkDownItMarkDownService(),
|
||||
encryptionService: encryptionService,
|
||||
encryptionService,
|
||||
notificationService: ns,
|
||||
userModel: userModel,
|
||||
chatStompClient,
|
||||
};
|
||||
const chatView = new ChatView(cvDeps);
|
||||
chatModel.attach(chatView);
|
||||
const chatController = new ChatController(chatModel, chatView);
|
||||
const chatController = new ChatController({
|
||||
model: chatModel,
|
||||
view: chatView,
|
||||
chatStompClient,
|
||||
encryptionService,
|
||||
});
|
||||
|
||||
const uvDeps: UserViewDeps = {
|
||||
model: userModel,
|
||||
@ -78,7 +108,7 @@ const uvDeps: UserViewDeps = {
|
||||
const userView = new UserView(uvDeps);
|
||||
userModel.attach(userView);
|
||||
const userController = new UserController(userModel, userView);
|
||||
userController.getActiveUsers();
|
||||
// userController.getActiveUsers();
|
||||
|
||||
// @ts-ignore
|
||||
Handlebars.registerHelper("moment", require("helper-moment"));
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Subject } from "../observe/Observable";
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { ObserverData } from "../observe/ObserverData";
|
||||
import { JsonAPI } from "../singleton/JsonAPI";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { ChatModelHelper } from "./ChatModelHelper";
|
||||
import log = require("loglevel");
|
||||
import { ObserverData } from "../observe/ObserverData";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import moment = require("moment");
|
||||
import log from "loglevel";
|
||||
import moment from "moment";
|
||||
|
||||
interface Params {
|
||||
userName: string;
|
||||
@ -44,7 +44,7 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
|
||||
log.info("Subject: Detached an observer.");
|
||||
}
|
||||
|
||||
private storeUserMessages(
|
||||
public storeUserMessages(
|
||||
username: string,
|
||||
messages: ChatMessageViewModel[],
|
||||
op: string
|
||||
@ -52,25 +52,28 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
|
||||
switch (op) {
|
||||
case "clear":
|
||||
this._messagesMap.set(username, []);
|
||||
this.notify({ userName: username, data: [], op: op });
|
||||
break;
|
||||
case "page":
|
||||
this._messagesMap.set(
|
||||
username,
|
||||
messages.concat(this.getStoredUserMessages(username))
|
||||
);
|
||||
this.notify({ userName: username, data: messages, op: op });
|
||||
break;
|
||||
// case "page": this._messagesMap.set(username, messages);
|
||||
case "new":
|
||||
this._messagesMap.set(
|
||||
username,
|
||||
this.getStoredUserMessages(username).concat(messages)
|
||||
);
|
||||
this.notify({ userName: username, data: messages, op: op });
|
||||
break;
|
||||
case "update":
|
||||
this._messagesMap.set(
|
||||
username,
|
||||
this.getStoredUserMessages(username).concat(messages)
|
||||
);
|
||||
this.notify({ userName: username, data: messages, op: op });
|
||||
break;
|
||||
default:
|
||||
new Error("Invalid option");
|
||||
@ -102,47 +105,18 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "new":
|
||||
{
|
||||
const od: ObserverData<ChatMessageViewModel> = {
|
||||
data: p.data,
|
||||
op: p.op,
|
||||
};
|
||||
for (const observer of this._observers) {
|
||||
observer.update(od);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "page":
|
||||
{
|
||||
const od: ObserverData<ChatMessageViewModel> = {
|
||||
data: p.data,
|
||||
op: p.op,
|
||||
};
|
||||
for (const observer of this._observers) {
|
||||
observer.update(od);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "update":
|
||||
{
|
||||
const od: ObserverData<ChatMessageViewModel> = {
|
||||
data: p.data,
|
||||
op: p.op,
|
||||
};
|
||||
for (const observer of this._observers) {
|
||||
observer.update(od);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
log.error("error");
|
||||
const od: ObserverData<ChatMessageViewModel> = {
|
||||
data: p.data,
|
||||
op: p.op,
|
||||
};
|
||||
for (const observer of this._observers) {
|
||||
observer.update(od);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void {}
|
||||
|
||||
public clear(): void {
|
||||
log.info("Clearing model");
|
||||
this._messagePageMap.set(JsonAPI.contactName!, 0);
|
||||
@ -200,7 +174,7 @@ export class ChatModel implements Subject<ChatMessageViewModel> {
|
||||
}
|
||||
|
||||
this.storeUserMessages(aVm.userName!, cVMsFiltered, op);
|
||||
this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
|
||||
// this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
|
||||
}
|
||||
|
||||
return cVMsFiltered;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as log from "loglevel";
|
||||
import log from "loglevel";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import { Sprintf } from "../../common/global/Sprintf";
|
||||
import { EncryptionService } from "../../common/service/EncryptionService";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { JsonAPI } from "../singleton/JsonAPI";
|
||||
import { Sprintf } from "../../common/global/Sprintf";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { fetchErrorHandler } from "./FetchErrorHandler";
|
||||
|
||||
@ -88,7 +88,7 @@ export class ChatModelHelper {
|
||||
const vm = new ChatMessageViewModel();
|
||||
vm.fromUser = chatMessageDTO.fromUser;
|
||||
vm.toUser = chatMessageDTO.toUser;
|
||||
vm.messageTime = chatMessageDTO.messageTime;
|
||||
vm.messageTime = new Date(chatMessageDTO.messageTime);
|
||||
vm.message = await this._encryptionService.decryptAsPromise(
|
||||
passphrase,
|
||||
chatMessageDTO.messageCipher
|
||||
@ -106,7 +106,7 @@ export class ChatModelHelper {
|
||||
// vm.messageTime = chatMessageDTO.messageTime;
|
||||
chatMessageDTO.messageTime == null
|
||||
? log.error("Message time somehow null")
|
||||
: (vm.messageTime = chatMessageDTO.messageTime);
|
||||
: (vm.messageTime = new Date(chatMessageDTO.messageTime));
|
||||
vm.message = this._encryptionService.decrypt(
|
||||
passphrase,
|
||||
chatMessageDTO.messageCipher
|
||||
|
@ -1,11 +1,21 @@
|
||||
import * as log from "loglevel";
|
||||
import log from "loglevel";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { Subject } from "../observe/Observable";
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { JsonAPI } from "../singleton/JsonAPI";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { fetchErrorHandler } from "./FetchErrorHandler";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { EncryptionService } from "../../common/service/EncryptionService";
|
||||
import { createApiHeaders } from "../../common/ajax/util";
|
||||
import { getActiveUsers } from "../../common/ajax/Users";
|
||||
|
||||
export interface Props {
|
||||
encryptionService: EncryptionService;
|
||||
notificationService: NotificationService;
|
||||
authToken: string;
|
||||
}
|
||||
export class UserModel implements Subject<ActiveUserViewModel> {
|
||||
/**
|
||||
* @type {Observer[]} List of subscribers. In real life, the list of
|
||||
@ -14,12 +24,18 @@ export class UserModel implements Subject<ActiveUserViewModel> {
|
||||
*/
|
||||
private readonly observers: Observer<ActiveUserViewModel>[] = [];
|
||||
|
||||
private _activeUsersList: ActiveUserViewModel[] = Array();
|
||||
private readonly _notificationService: NotificationService;
|
||||
private readonly _activeUsersList: ActiveUserViewModel[] = Array();
|
||||
private readonly _props: Props;
|
||||
|
||||
constructor(notificationService: NotificationService) {
|
||||
constructor(props: Props) {
|
||||
this._activeUsersList = [];
|
||||
this._notificationService = notificationService;
|
||||
this._props = props;
|
||||
|
||||
getActiveUsers(props.authToken).then((data) => {
|
||||
log.debug(data);
|
||||
this._activeUsersList.push(...data);
|
||||
this.notify();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,63 +70,16 @@ export class UserModel implements Subject<ActiveUserViewModel> {
|
||||
}
|
||||
}
|
||||
|
||||
public someBusinessMethod(activeuserList: ActiveUserViewModel[]): void {
|
||||
this._activeUsersList = activeuserList;
|
||||
this.helperMethod();
|
||||
log.info(`Subject: My state has just changed`);
|
||||
log.trace(activeuserList);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
updateLastActive(username: String, lastActive: Date): void {
|
||||
this._activeUsersList
|
||||
.filter(u => u.userName == username)
|
||||
.forEach(u => u.lastActive = lastActive)
|
||||
.filter((u) => u.userName == username)
|
||||
.forEach((u) => (u.lastActive = lastActive));
|
||||
}
|
||||
|
||||
/**
|
||||
* getActiveUsers
|
||||
*/
|
||||
public getActiveUsers(): void {
|
||||
if (JsonAPI.authToken != null) {
|
||||
this._getActiveUsersAjax(JsonAPI.authToken).then((data) => {
|
||||
// // activeUsers = data;
|
||||
// sessionStorage.setItem('activeUsers', JSON.stringify(data));
|
||||
// log.trace(sessionStorage.getItem('activeUsers'));
|
||||
log.info(`Subject: received ajax active users`);
|
||||
data.map((d: any) => {
|
||||
if (d.lastActive == null) return null;
|
||||
|
||||
d.lastActive = new Date(d.lastActive);
|
||||
return d;
|
||||
});
|
||||
this._activeUsersList = data;
|
||||
this.notify();
|
||||
});
|
||||
} else {
|
||||
log.error("Auth token is null");
|
||||
}
|
||||
public decryptForUser(dto: ChatMessageDTO, userName: string): string {
|
||||
const passphrase =
|
||||
this._activeUsersList.find((u) => u.userName === userName)?.passphrase ||
|
||||
"";
|
||||
return this._props.encryptionService.decrypt(passphrase, dto.messageCipher);
|
||||
}
|
||||
|
||||
private async _getActiveUsersAjax(authToken: string): Promise<any> {
|
||||
let headers = new Headers();
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
let response = await fetch(JsonAPI.ACTIVE_USERS_GET, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
});
|
||||
log.debug(response.clone());
|
||||
if (fetchErrorHandler(response.clone(), this._notificationService)) {
|
||||
return null;
|
||||
}
|
||||
let data = await response.json();
|
||||
// return data;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (data != null) {
|
||||
resolve(data);
|
||||
} else reject("Response data null");
|
||||
});
|
||||
}
|
||||
|
||||
private helperMethod() { }
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import * as DOMPurify from "dompurify";
|
||||
import * as log from "loglevel";
|
||||
import DOMPurify from "dompurify";
|
||||
import log from "loglevel";
|
||||
import { ChatMessageDTO } from "../../common/dto/ChatMessageDTO";
|
||||
import { MessageCipherDTO } from "../../common/dto/MessageCipherDTO";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { EncryptionService } from "../../common/service/EncryptionService";
|
||||
import { MarkDownService } from "../../common/service/MarkDownService";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import { UserModel } from "../model/UserModel";
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { ObserverData } from "../observe/ObserverData";
|
||||
import { JsonAPI } from "../singleton/JsonAPI";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { ChatViewDeps } from "./ChatViewDeps";
|
||||
import { fetchHandler } from "./FetchHandler";
|
||||
import { ObserverData } from "../observe/ObserverData";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { UserModel } from "../model/UserModel";
|
||||
|
||||
export class ChatView implements Observer<ChatMessageViewModel> {
|
||||
private readonly _chatModel: ChatModel;
|
||||
@ -27,6 +27,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
|
||||
private readonly _encryptionService: EncryptionService;
|
||||
private readonly _notificationService: NotificationService;
|
||||
private readonly _userModel: UserModel;
|
||||
private readonly _deps: ChatViewDeps;
|
||||
|
||||
constructor(deps: ChatViewDeps) {
|
||||
this._messageContainer = deps.messageContainer;
|
||||
@ -37,6 +38,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
|
||||
this._encryptionService = deps.encryptionService;
|
||||
this._notificationService = deps.notificationService;
|
||||
this._userModel = deps.userModel;
|
||||
this._deps = deps;
|
||||
this._initEventListeners();
|
||||
|
||||
$(document).ready(function () {
|
||||
@ -181,7 +183,7 @@ export class ChatView implements Observer<ChatMessageViewModel> {
|
||||
};
|
||||
|
||||
this.update({ data: new Array(context), op: "new" });
|
||||
this._userModel.updateLastActive(contactName, msgTime)
|
||||
this._userModel.updateLastActive(contactName, msgTime);
|
||||
this._userModel.notify();
|
||||
|
||||
let messageCipher: MessageCipherDTO = this._encryptionService.encrypt(
|
||||
@ -194,28 +196,17 @@ export class ChatView implements Observer<ChatMessageViewModel> {
|
||||
messageCipher: messageCipher,
|
||||
messageTime: msgTime.toISOString(),
|
||||
};
|
||||
this._sendMessageAJAX(chatMessageDTO);
|
||||
// this._sendMessageAJAX(chatMessageDTO);
|
||||
this._sendMessageWS(chatMessageDTO);
|
||||
}
|
||||
|
||||
private _sendMessageAJAX(chatMessageDTO: any): void {
|
||||
let headers = new Headers();
|
||||
// console.log("Token = " + btoa("hmm" + ":" + "hmm"))
|
||||
|
||||
// headers.append('Accept','application/json')
|
||||
headers.append("Content-Type", "application/json");
|
||||
// headers.append('Authorization', basicAuthToken);
|
||||
// @ts-ignore
|
||||
headers.append("X-AUTH-TOKEN", JsonAPI.authToken);
|
||||
fetch(JsonAPI.MESSAGE_POST, {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
private _sendMessageWS(chatMessageDTO: ChatMessageDTO): void {
|
||||
// if (this._deps.chatStompClinet.connected) {
|
||||
this._deps.chatStompClient.publish({
|
||||
body: JSON.stringify(chatMessageDTO),
|
||||
})
|
||||
.then((response) => {
|
||||
log.debug(response);
|
||||
return response.clone();
|
||||
})
|
||||
.then((response) => fetchHandler(response, this._notificationService));
|
||||
destination: "/app/chat",
|
||||
});
|
||||
// }
|
||||
}
|
||||
|
||||
private _chatMessagePageLoadAjax() {
|
||||
|
@ -4,14 +4,16 @@ import { MarkDownService } from "../../common/service/MarkDownService";
|
||||
import { EncryptionService } from "../../common/service/EncryptionService";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { UserModel } from "../model/UserModel";
|
||||
import { Client } from "@stomp/stompjs";
|
||||
|
||||
export interface ChatViewDeps {
|
||||
chatModel: ChatModel;
|
||||
messageContainer: HTMLElement;
|
||||
messageSendTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
|
||||
messageReceiveTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
|
||||
markdownService: MarkDownService;
|
||||
encryptionService: EncryptionService;
|
||||
notificationService: NotificationService;
|
||||
userModel: UserModel
|
||||
}
|
||||
chatModel: ChatModel;
|
||||
messageContainer: HTMLElement;
|
||||
messageSendTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
|
||||
messageReceiveTemplate: Handlebars.TemplateDelegate<ChatMessageViewModel>;
|
||||
markdownService: MarkDownService;
|
||||
encryptionService: EncryptionService;
|
||||
notificationService: NotificationService;
|
||||
userModel: UserModel;
|
||||
chatStompClient: Client;
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import log = require("loglevel");
|
||||
import * as DOMPurify from "dompurify";
|
||||
import DOMPurify from "dompurify";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { SearchService } from "../../common/service/SearchService";
|
||||
import { ChatModel } from "../model/ChatModel";
|
||||
import { UserModel } from "../model/UserModel";
|
||||
import { UserViewDeps } from "./UserViewDeps";
|
||||
import { Observer } from "../observe/Observer";
|
||||
import { ObserverData } from "../observe/ObserverData";
|
||||
import { JsonAPI } from "../singleton/JsonAPI";
|
||||
import { NotificationService } from "../../common/service/NotificationService";
|
||||
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
|
||||
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
|
||||
import { UserViewDeps } from "./UserViewDeps";
|
||||
import log from "loglevel";
|
||||
|
||||
export class UserView implements Observer<ActiveUserViewModel> {
|
||||
private readonly _model: UserModel;
|
||||
@ -89,58 +89,63 @@ export class UserView implements Observer<ActiveUserViewModel> {
|
||||
JsonAPI.contactName = userName;
|
||||
// @ts-ignore: Object is possibly 'null'.
|
||||
document.getElementById("user-name-span").innerText = userName;
|
||||
let currentUser = this._model.activeUsersList.find((vm) => vm.userName === userName) || new ActiveUserViewModel();
|
||||
let currentUser =
|
||||
this._model.activeUsersList.find((vm) => vm.userName === userName) ||
|
||||
new ActiveUserViewModel();
|
||||
currentUser.userName = userName;
|
||||
|
||||
if (!currentUser?.passphrase) {
|
||||
this._notificationService.passphrasePrompt(
|
||||
async (result) => {
|
||||
if (result) {
|
||||
|
||||
const valid = await this._chatModel.isPassphraseValid(result, currentUser.userName!);
|
||||
if (!valid) {
|
||||
bootbox.alert("Some error occured. Please check your password");
|
||||
log.error("invalid password");
|
||||
return;
|
||||
}
|
||||
currentUser.unlocked = true;
|
||||
currentUser.passphrase = result;
|
||||
const chatMessages: ChatMessageViewModel[] = await this._chatModel.getMessages(currentUser, "new");
|
||||
|
||||
this._model.activeUsersList
|
||||
.filter((v) => v.userName == currentUser!.userName)
|
||||
.forEach((v) => {
|
||||
v.passphrase = result;
|
||||
v.unlocked = true;
|
||||
if (chatMessages.length > 0) {
|
||||
v.lastMessageTime = new Date(
|
||||
chatMessages[chatMessages.length - 1].messageTime
|
||||
);
|
||||
const lastMessageText = (v.lastMessageText =
|
||||
chatMessages[chatMessages.length - 1].message);
|
||||
if (lastMessageText.length > 15) {
|
||||
v.lastMessageText =
|
||||
chatMessages[chatMessages.length - 1].message.slice(
|
||||
0,
|
||||
15
|
||||
) + "...";
|
||||
}
|
||||
}
|
||||
});
|
||||
this._promptHandler(currentUser);
|
||||
|
||||
this._notificationService.passphrasePrompt(async (result) => {
|
||||
if (result) {
|
||||
const valid = await this._chatModel.isPassphraseValid(
|
||||
result,
|
||||
currentUser.userName!
|
||||
);
|
||||
if (!valid) {
|
||||
bootbox.alert("Some error occured. Please check your password");
|
||||
log.error("invalid password");
|
||||
return;
|
||||
}
|
||||
currentUser.unlocked = true;
|
||||
currentUser.passphrase = result;
|
||||
const chatMessages: ChatMessageViewModel[] = await this._chatModel.getMessages(
|
||||
currentUser,
|
||||
"new"
|
||||
);
|
||||
|
||||
this._model.activeUsersList
|
||||
.filter((v) => v.userName == currentUser!.userName)
|
||||
.forEach((v) => {
|
||||
v.passphrase = result;
|
||||
v.unlocked = true;
|
||||
if (chatMessages.length > 0) {
|
||||
v.lastMessageTime = new Date(
|
||||
chatMessages[chatMessages.length - 1].messageTime
|
||||
);
|
||||
const lastMessageText = (v.lastMessageText =
|
||||
chatMessages[chatMessages.length - 1].message);
|
||||
if (lastMessageText.length > 15) {
|
||||
v.lastMessageText =
|
||||
chatMessages[chatMessages.length - 1].message.slice(0, 15) +
|
||||
"...";
|
||||
}
|
||||
}
|
||||
});
|
||||
this._promptHandler(currentUser);
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
});
|
||||
} else {
|
||||
this._chatModel.getMessages(currentUser, "new");
|
||||
}
|
||||
|
||||
el.className += " active";
|
||||
if (currentUser.unlocked && currentUser.lastMessageTime) {
|
||||
this._newMessagesLoop = setInterval(
|
||||
this._chatModel.getMessages.bind(this._chatModel, currentUser, "update"),
|
||||
this._chatModel.getMessages.bind(
|
||||
this._chatModel,
|
||||
currentUser,
|
||||
"update"
|
||||
),
|
||||
10_000
|
||||
);
|
||||
this._model.notify();
|
||||
@ -177,9 +182,8 @@ export class UserView implements Observer<ActiveUserViewModel> {
|
||||
let searchResult = this._searchService.search(list, searchTerm);
|
||||
this.update({ data: searchResult, op: "" });
|
||||
log.debug(searchResult);
|
||||
}
|
||||
else {
|
||||
this._notificationService.error("Please enter a name")
|
||||
} else {
|
||||
this._notificationService.error("Please enter a name");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -28,17 +28,11 @@ export async function sendReencryptedMessages(
|
||||
rencryptionDTOs: ReencryptionDTO[],
|
||||
authToken: string
|
||||
) {
|
||||
let headers = new Headers();
|
||||
// console.log("Token = " + btoa("hmm" + ":" + "hmm"))
|
||||
|
||||
// headers.append('Accept','application/json')
|
||||
headers.append("Content-Type", "application/json");
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
fetch(Routes.Admin.reencrypt, {
|
||||
return fetch(Routes.Admin.reencrypt, {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
headers: createApiHeaders(authToken),
|
||||
body: JSON.stringify(rencryptionDTOs),
|
||||
}).then((response) => console.log(response));
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOneMessage(
|
||||
@ -52,10 +46,6 @@ export async function getOneMessage(
|
||||
method: "GET",
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
log.debug(response.clone());
|
||||
// if (fetchErrorHandler(response.clone(), this._notificationService)) {
|
||||
// return null;
|
||||
// }
|
||||
const data: Promise<any> = await response.json();
|
||||
function func(data: any) {
|
||||
const d1 = data.map((d: any) => {
|
||||
@ -77,3 +67,15 @@ export async function getStats(authToken: string) {
|
||||
});
|
||||
return (await response.json()) as StatsDTO;
|
||||
}
|
||||
|
||||
export async function sendMessageAJAX(
|
||||
chatMessageDTO: ChatMessageDTO,
|
||||
authToken: string
|
||||
) {
|
||||
const response = fetch(JsonAPI.MESSAGE_POST, {
|
||||
method: "POST",
|
||||
headers: createApiHeaders(authToken),
|
||||
body: JSON.stringify(chatMessageDTO),
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Routes } from "../routes/Routes";
|
||||
import { createApiHeaders } from "./util";
|
||||
import { AdminUserDTO } from "../dto/AdminUserDTO";
|
||||
import { ActiveUserViewModel } from "../../chat/viewmodel/ActiveUserViewModel";
|
||||
import { JsonAPI } from "../../chat/singleton/JsonAPI";
|
||||
|
||||
export async function getOtherUsers(
|
||||
authToken: string
|
||||
@ -13,3 +15,20 @@ export async function getOtherUsers(
|
||||
let data = (await response.json()) as AdminUserDTO[];
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getActiveUsers(
|
||||
authToken: string
|
||||
): Promise<ActiveUserViewModel[]> {
|
||||
const response = await fetch(JsonAPI.ACTIVE_USERS_GET, {
|
||||
method: "GET",
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
const data = await response.json();
|
||||
const data2 = data.map((d: any) => {
|
||||
if (d.lastActive == null) return d;
|
||||
|
||||
d.lastActive = new Date(d.lastActive);
|
||||
return d;
|
||||
}) as ActiveUserViewModel[];
|
||||
return data2;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { MessageCipherDTO } from "./MessageCipherDTO";
|
||||
|
||||
export class ChatMessageDTO {
|
||||
public toUser: string = "";
|
||||
public fromUser: string = "";
|
||||
public messageCipher!: MessageCipherDTO;
|
||||
public messageTime: Date = new Date();
|
||||
export interface ChatMessageDTO {
|
||||
toUser: string;
|
||||
fromUser: string;
|
||||
messageCipher: MessageCipherDTO;
|
||||
messageTime: string;
|
||||
}
|
||||
|
@ -1,40 +1,12 @@
|
||||
// export class MessageCipherDTO {
|
||||
// // iv!: string;
|
||||
// // v!: number;
|
||||
// // iterations!: number;
|
||||
// // keySize!: number;
|
||||
// // tagSize!: number;
|
||||
// // mode!: string;
|
||||
// // adata!: string;
|
||||
// // cipher!: string;
|
||||
// // salt!: string;
|
||||
// // cipherText!: string;
|
||||
|
||||
// iv!: string;
|
||||
// v!: number;
|
||||
// iter!: number;
|
||||
// ks!: number;
|
||||
// ts!: number;
|
||||
// mode!: string;
|
||||
// adata!: string;
|
||||
// cipher!: string;
|
||||
// salt!: string;
|
||||
// ct!: string;
|
||||
|
||||
// // public toMessageCipherDTO {
|
||||
|
||||
// // }
|
||||
// }
|
||||
|
||||
export interface MessageCipherDTO {
|
||||
iv: string,
|
||||
v: number,
|
||||
iter: number,
|
||||
ks: number,
|
||||
ts: number,
|
||||
mode: string,
|
||||
adata: string,
|
||||
cipher: string,
|
||||
salt: string,
|
||||
ct: string,
|
||||
}
|
||||
iv: string;
|
||||
v: number;
|
||||
iter: number;
|
||||
ks: number;
|
||||
ts: number;
|
||||
mode: string;
|
||||
adata: string;
|
||||
cipher: string;
|
||||
salt: string;
|
||||
ct: string;
|
||||
}
|
||||
|
12
src/main/java/org/ros/chatto/websocket/Message.java
Normal file
12
src/main/java/org/ros/chatto/websocket/Message.java
Normal file
@ -0,0 +1,12 @@
|
||||
package org.ros.chatto.websocket;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class Message {
|
||||
private String from;
|
||||
private String to;
|
||||
private String text;
|
||||
}
|
26
src/main/java/org/ros/chatto/websocket/WebSocketConfig.java
Normal file
26
src/main/java/org/ros/chatto/websocket/WebSocketConfig.java
Normal file
@ -0,0 +1,26 @@
|
||||
package org.ros.chatto.websocket;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.enableSimpleBroker("/topic", "/queue");
|
||||
config.setApplicationDestinationPrefixes("/app");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/ping");
|
||||
registry.addEndpoint("/ping").withSockJS();
|
||||
registry.addEndpoint("/chat");
|
||||
registry.addEndpoint("/chat").withSockJS();
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.ros.chatto.websocket;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.ros.chatto.dto.ChatMessageDTO;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class WebSocketController {
|
||||
|
||||
private final SimpMessagingTemplate smt;
|
||||
|
||||
@MessageMapping("/ping")
|
||||
public void send2(Principal principal) throws Exception {
|
||||
smt.convertAndSendToUser(principal.getName(), "/queue/ping", "pong");
|
||||
}
|
||||
|
||||
@MessageMapping("/chat")
|
||||
public void send(@Valid @Payload final ChatMessageDTO message)
|
||||
throws Exception {
|
||||
smt.convertAndSendToUser(message.getFromUser(), "/queue/reply",
|
||||
message);
|
||||
smt.convertAndSendToUser(message.getToUser(), "/queue/reply", message);
|
||||
}
|
||||
|
||||
@GetMapping("/ws")
|
||||
public String wsPage() {
|
||||
return "ws";
|
||||
}
|
||||
}
|
@ -57,6 +57,13 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/loglevel/1.6.4/loglevel.min.js"
|
||||
integrity="sha256-ACTlnmNCkOooSKkPCKYbiex8WLE82aeiN+Z9ElZag5Q=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.0/sockjs.min.js"
|
||||
integrity="sha512-5yJ548VSnLflcRxWNqVWYeQZnby8D8fJTmYRLyvs445j1XmzR8cnWi85lcHx3CUEeAX+GrK3TqTfzOO6LKDpdw=="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"
|
||||
integrity="sha512-iKDtgDyTHjAitUDdLljGhenhPwrbBfqTKWO1mkhSFH3A7blITC9MhYon6SjnMhp4o0rADGw9yAC6EW4t5a4K3g=="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<link th:href="@{/css/master.css}" href="../../resources/static/css/master.css" rel="stylesheet">
|
||||
<link th:href="@{/css/colors.css}" href="../../resources/static/css/colors.css" rel="stylesheet">
|
||||
|
||||
|
83
src/main/resources/templates/ws.html
Normal file
83
src/main/resources/templates/ws.html
Normal file
@ -0,0 +1,83 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Chat WebSocket</title>
|
||||
<script type="text/javascript">
|
||||
var stompClient = null;
|
||||
|
||||
function setConnected(connected) {
|
||||
document.getElementById('connect').disabled = connected;
|
||||
document.getElementById('disconnect').disabled = !connected;
|
||||
document.getElementById('conversationDiv').style.visibility
|
||||
= connected ? 'visible' : 'hidden';
|
||||
document.getElementById('response').innerHTML = '';
|
||||
}
|
||||
|
||||
function connect() {
|
||||
var socket = new SockJS('/chat2');
|
||||
stompClient = Stomp.over(socket);
|
||||
stompClient.connect({}, function (frame) {
|
||||
setConnected(true);
|
||||
console.log('Connected: ' + frame);
|
||||
// stompClient.subscribe('/topic/messages', function (messageOutput) {
|
||||
// showMessageOutput(JSON.parse(messageOutput.body));
|
||||
// });
|
||||
stompClient.subscribe('/user/queue/reply', function (messageOutput) {
|
||||
showMessageOutput(JSON.parse(messageOutput.body));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (stompClient != null) {
|
||||
stompClient.disconnect();
|
||||
}
|
||||
setConnected(false);
|
||||
console.log("Disconnected");
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
var from = document.getElementById('from').value;
|
||||
var to = document.getElementById('to').value;
|
||||
var text = document.getElementById('text').value;
|
||||
stompClient.send("/app/chat", {},
|
||||
JSON.stringify({ 'from': from, 'text': text, to: to }));
|
||||
}
|
||||
|
||||
function showMessageOutput(messageOutput) {
|
||||
var response = document.getElementById('response');
|
||||
var p = document.createElement('p');
|
||||
p.style.wordWrap = 'break-word';
|
||||
p.appendChild(document.createTextNode(messageOutput.from + ": "
|
||||
+ messageOutput.text + " (" + messageOutput.time + ")"));
|
||||
response.appendChild(p);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="disconnect()">
|
||||
<div>
|
||||
<div>
|
||||
<input type="text" id="from" placeholder="Choose a nickname" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="text" id="to" placeholder="Send to User" />
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button id="connect" onclick="connect();">Connect</button>
|
||||
<button id="disconnect" disabled="disabled" onclick="disconnect();">
|
||||
Disconnect
|
||||
</button>
|
||||
</div>
|
||||
<br />
|
||||
<div id="conversationDiv">
|
||||
<input type="text" id="text" placeholder="Write a message..." />
|
||||
<button id="sendMessage" onclick="sendMessage();">Send</button>
|
||||
<p id="response"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
104
yarn.lock
104
yarn.lock
@ -2,6 +2,11 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@stomp/stompjs@^5.4.4":
|
||||
version "5.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@stomp/stompjs/-/stompjs-5.4.4.tgz#f51d2edf9a00fac645dde3a494738d96ca17e5aa"
|
||||
integrity sha512-RIzQ7MLRSJLUpTHcje1ZclnHH982amJSKC9bDxGO0wyu5OF9ROuuiLf7TxKxo1zUu7lGEYNedg9SEi87uMWDqg==
|
||||
|
||||
"@types/bootbox@^5.2.0":
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/bootbox/-/bootbox-5.2.0.tgz#0e51344914dbe2fbb5c720b3bc2797c467d2115a"
|
||||
@ -75,6 +80,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/sjcl/-/sjcl-1.0.29.tgz#bd154ee15421fe24be2db4a20322f38069c1ca71"
|
||||
integrity sha512-gP9M7Oq4xAoDpuueWHeOTasccV4K6iFBEBPbR0tXejKT/e/Gkla0XcJ9REmgSCICt6y8/ubcvXFtiZ4ZLQ6BKA==
|
||||
|
||||
"@types/sockjs-client@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sockjs-client/-/sockjs-client-1.1.1.tgz#1ef133b5a79d51447a93ce16164706c0164b5548"
|
||||
integrity sha512-DaTdN4kfPNxu0otmQlxhmYeCjtY8cHmJsU6LqiFOrhytIkx8Txq06PwAWYzha7nMkEyju44a3NDpqCKiHn/NZQ==
|
||||
|
||||
"@types/sprintf-js@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/sprintf-js/-/sprintf-js-1.1.2.tgz#a4fcb84c7344f39f70dc4eec0e1e7f10a48597a3"
|
||||
@ -859,6 +869,13 @@ debug@^2.2.0, debug@^2.3.3:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@ -1061,6 +1078,13 @@ events@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5"
|
||||
integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==
|
||||
|
||||
eventsource@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
|
||||
integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
|
||||
dependencies:
|
||||
original "^1.0.0"
|
||||
|
||||
evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
|
||||
@ -1139,6 +1163,13 @@ fast-safe-stringify@^2.0.7:
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
|
||||
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
|
||||
|
||||
faye-websocket@^0.11.3:
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
|
||||
integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
|
||||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
@ -1552,6 +1583,11 @@ htmlescape@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
|
||||
integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=
|
||||
|
||||
http-parser-js@>=0.5.1:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77"
|
||||
integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==
|
||||
|
||||
https-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
@ -1584,7 +1620,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -1809,6 +1845,11 @@ json-stable-stringify@~0.0.0:
|
||||
dependencies:
|
||||
jsonify "~0.0.0"
|
||||
|
||||
json3@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
|
||||
integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
|
||||
|
||||
jsonify@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||
@ -2084,6 +2125,11 @@ ms@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
nan@^2.12.1:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
@ -2193,6 +2239,13 @@ optimist@^0.6.1:
|
||||
minimist "~0.0.1"
|
||||
wordwrap "~0.0.2"
|
||||
|
||||
original@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
|
||||
integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
|
||||
dependencies:
|
||||
url-parse "^1.4.3"
|
||||
|
||||
os-browserify@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
|
||||
@ -2377,6 +2430,11 @@ querystring@0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
|
||||
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
@ -2522,6 +2580,11 @@ replace-requires@~1.0.3:
|
||||
patch-text "~1.0.2"
|
||||
xtend "~4.0.0"
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resolve-dir@^1.0.0, resolve-dir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
|
||||
@ -2579,6 +2642,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
safe-buffer@>=5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
@ -2689,6 +2757,18 @@ snapdragon@^0.8.1:
|
||||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
sockjs-client@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add"
|
||||
integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q==
|
||||
dependencies:
|
||||
debug "^3.2.6"
|
||||
eventsource "^1.0.7"
|
||||
faye-websocket "^0.11.3"
|
||||
inherits "^2.0.4"
|
||||
json3 "^3.3.3"
|
||||
url-parse "^1.4.7"
|
||||
|
||||
source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
|
||||
@ -3082,6 +3162,14 @@ urix@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
|
||||
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
|
||||
|
||||
url-parse@^1.4.3, url-parse@^1.4.7:
|
||||
version "1.4.7"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
|
||||
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
url@~0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||
@ -3140,6 +3228,20 @@ watchify@^3.6.1:
|
||||
through2 "^2.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
websocket-driver@>=0.5.1:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
|
||||
integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
|
||||
dependencies:
|
||||
http-parser-js ">=0.5.1"
|
||||
safe-buffer ">=5.1.0"
|
||||
websocket-extensions ">=0.1.1"
|
||||
|
||||
websocket-extensions@>=0.1.1:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
|
||||
|
||||
which@^1.2.14, which@~1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
|
Loading…
Reference in New Issue
Block a user