Compare commits

...

7 Commits

Author SHA1 Message Date
Rohan Sircar de2ad81f8e Improve frontend api part 1 4 years ago
Rohan Sircar e4041f9e41 Updated websocket ts code 4 years ago
Rohan Sircar 00a01bc81b Organized imports 4 years ago
Rohan Sircar 2b3d6009f1 First integration of websocket into chat 4 years ago
Rohan Sircar 21fa7b5e64 Added new websocket endpoint 4 years ago
Rohan Sircar 5f01174764 Adjusted websocket deps 4 years ago
Rohan Sircar 56fd5e8c89 initial websocket commit 4 years ago
  1. 8
      package.json
  2. 20
      pom.xml
  3. 56
      src/main/frontend/chat/component/ChatComponent.ts
  4. 67
      src/main/frontend/chat/controller/ChatController.ts
  5. 64
      src/main/frontend/chat/controller/UserController.ts
  6. 52
      src/main/frontend/chat/main.ts
  7. 60
      src/main/frontend/chat/model/ChatModel.ts
  8. 8
      src/main/frontend/chat/model/ChatModelHelper.ts
  9. 89
      src/main/frontend/chat/model/UserModel.ts
  10. 45
      src/main/frontend/chat/view/ChatView.ts
  11. 20
      src/main/frontend/chat/view/ChatViewDeps.ts
  12. 106
      src/main/frontend/chat/view/UserView.ts
  13. 28
      src/main/frontend/common/ajax/Messages.ts
  14. 19
      src/main/frontend/common/ajax/Users.ts
  15. 10
      src/main/frontend/common/dto/ChatMessageDTO.ts
  16. 50
      src/main/frontend/common/dto/MessageCipherDTO.ts
  17. 12
      src/main/java/org/ros/chatto/websocket/Message.java
  18. 26
      src/main/java/org/ros/chatto/websocket/WebSocketConfig.java
  19. 39
      src/main/java/org/ros/chatto/websocket/WebSocketController.java
  20. 7
      src/main/resources/templates/fragments/head.html
  21. 83
      src/main/resources/templates/ws.html
  22. 104
      yarn.lock

8
package.json

@ -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

@ -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

@ -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
// )
// );
});
};
}
}

67
src/main/frontend/chat/controller/ChatController.ts

@ -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;
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);
}
}
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);
}
public store(userName: string, message: ChatMessageViewModel): void {
this._props.model.storeUserMessages(userName, Array(message), "new");
}
}

64
src/main/frontend/chat/controller/UserController.ts

@ -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;
constructor(model: UserModel, view: UserView) {
this._model = model;
this._view = view;
}
/**
* 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();
}
}
private _model: UserModel;
private _view: UserView;
constructor(model: UserModel, view: UserView) {
this._model = model;
this._view = view;
}
// public getActiveUsers(): void {
// this._model.getActiveUsers();
// }
decryptForUser(dto: ChatMessageDTO, userName: string): string {
return this._model.decryptForUser(dto, userName);
}
}

52
src/main/frontend/chat/main.ts

@ -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"));

60
src/main/frontend/chat/model/ChatModel.ts

@ -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;

8
src/main/frontend/chat/model/ChatModelHelper.ts

@ -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

89
src/main/frontend/chat/model/UserModel.ts

@ -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");
}
}
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");
});
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 helperMethod() { }
}

45
src/main/frontend/chat/view/ChatView.ts

@ -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() {

20
src/main/frontend/chat/view/ChatViewDeps.ts

@ -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;
}

106
src/main/frontend/chat/view/UserView.ts

@ -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
src/main/frontend/common/ajax/Messages.ts

@ -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;
}

19
src/main/frontend/common/ajax/Users.ts

@ -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;
}

10
src/main/frontend/common/dto/ChatMessageDTO.ts

@ -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;
}

50
src/main/frontend/common/dto/MessageCipherDTO.ts

@ -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

@ -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

@ -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();
}
}

39
src/main/java/org/ros/chatto/websocket/WebSocketController.java

@ -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";
}
}

7
src/main/resources/templates/fragments/head.html

@ -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

@ -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

@ -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…
Cancel
Save