Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
SkuciSe
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tim 2 - 2022
SkuciSe
Commits
5aa65891
Commit
5aa65891
authored
Sep 17, 2022
by
Milovan Samardzic
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WebSocketConfig
parent
fd4f4f00
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
367 additions
and
0 deletions
+367
-0
SkuciSe/pom.xml
+4
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/Message.java
+14
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/MessageController.java
+23
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/ResponseMessage.java
+14
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/UserHandshakeHandler.java
+20
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WSController.java
+20
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WSService.java
+21
-0
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WebSocketConfig.java
+23
-0
SkuciSe/src/main/java/com/example/SkuciSe/controller/AppController.java
+8
-0
SkuciSe/src/main/resources/static/js/socket.js
+30
-0
SkuciSe/src/main/resources/templates/chat.html
+190
-0
No files found.
SkuciSe/pom.xml
View file @
5aa65891
...
@@ -60,6 +60,10 @@
...
@@ -60,6 +60,10 @@
<artifactId>
spring-security-test
</artifactId>
<artifactId>
spring-security-test
</artifactId>
<scope>
test
</scope>
<scope>
test
</scope>
</dependency>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-websocket
</artifactId>
</dependency>
</dependencies>
</dependencies>
<build>
<build>
...
...
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/Message.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
lombok.AllArgsConstructor
;
import
lombok.Getter
;
import
lombok.NoArgsConstructor
;
import
lombok.Setter
;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public
class
Message
{
private
String
messageContent
;
}
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/MessageController.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
com.example.SkuciSe.model.korisnik.Korisnik
;
import
org.springframework.messaging.handler.annotation.MessageMapping
;
import
org.springframework.messaging.simp.annotation.SendToUser
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.web.util.HtmlUtils
;
import
java.security.Principal
;
@Controller
public
class
MessageController
{
@MessageMapping
(
"/private-message"
)
@SendToUser
(
"topic/private-messages"
)
public
ResponseMessage
getPrivateMessage
(
final
Message
message
,
final
Principal
principal
)
throws
InterruptedException
{
Thread
.
sleep
(
1000
);
return
new
ResponseMessage
(
HtmlUtils
.
htmlEscape
(
"Sending private message to user "
+
principal
.
getName
()
+
": "
+
message
.
getMessageContent
())
);
}
}
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/ResponseMessage.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
lombok.AllArgsConstructor
;
import
lombok.Getter
;
import
lombok.NoArgsConstructor
;
import
lombok.Setter
;
@AllArgsConstructor
@Getter
@Setter
@NoArgsConstructor
public
class
ResponseMessage
{
private
String
content
;
}
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/UserHandshakeHandler.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
com.sun.security.auth.UserPrincipal
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.http.server.ServerHttpRequest
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.web.socket.WebSocketHandler
;
import
org.springframework.web.socket.server.support.DefaultHandshakeHandler
;
import
java.security.Principal
;
import
java.util.Map
;
public
class
UserHandshakeHandler
extends
DefaultHandshakeHandler
{
private
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
UserHandshakeHandler
.
class
);
@Override
protected
Principal
determineUser
(
ServerHttpRequest
request
,
WebSocketHandler
wsHandler
,
Map
<
String
,
Object
>
attributes
)
{
return
new
UserPrincipal
(
SecurityContextHolder
.
getContext
().
getAuthentication
().
getName
());
}
}
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WSController.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.PathVariable
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RestController
;
@RestController
public
class
WSController
{
@Autowired
private
WSService
service
;
@PostMapping
(
"/send-private-message/{mail}"
)
public
void
sendPrivateMessage
(
@PathVariable
(
"mail"
)
final
String
mail
,
@RequestBody
final
Message
message
)
{
service
.
notifyUser
(
mail
,
message
.
getMessageContent
());
}
}
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WSService.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
WebSocket
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.messaging.simp.SimpMessagingTemplate
;
import
org.springframework.stereotype.Service
;
@Service
public
class
WSService
{
private
final
SimpMessagingTemplate
messagingTemplate
;
@Autowired
public
WSService
(
SimpMessagingTemplate
messagingTemplate
)
{
this
.
messagingTemplate
=
messagingTemplate
;
}
public
void
notifyUser
(
final
String
mail
,
final
String
message
)
{
ResponseMessage
response
=
new
ResponseMessage
(
message
);
messagingTemplate
.
convertAndSendToUser
(
mail
,
"/topic/private-messages"
,
response
);
}
}
\ No newline at end of file
SkuciSe/src/main/java/com/example/SkuciSe/configuration/WebSocket/WebSocketConfig.java
0 → 100644
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
configuration
.
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"
);
config
.
setApplicationDestinationPrefixes
(
"/ws"
);
}
@Override
public
void
registerStompEndpoints
(
StompEndpointRegistry
registry
)
{
registry
.
addEndpoint
(
"out-websocket"
)
.
withSockJS
();
}
}
SkuciSe/src/main/java/com/example/SkuciSe/controller/AppController.java
View file @
5aa65891
package
com
.
example
.
SkuciSe
.
controller
;
package
com
.
example
.
SkuciSe
.
controller
;
import
com.example.SkuciSe.configuration.EmailPostoji
;
import
com.example.SkuciSe.configuration.EmailPostoji
;
import
com.example.SkuciSe.configuration.WebSocket.Message
;
import
com.example.SkuciSe.model.korisnik.Korisnik
;
import
com.example.SkuciSe.model.korisnik.Korisnik
;
import
com.example.SkuciSe.model.korisnik.KorisnikDetails
;
import
com.example.SkuciSe.model.korisnik.KorisnikDetails
;
import
com.example.SkuciSe.model.oglas.Oglas
;
import
com.example.SkuciSe.model.oglas.Oglas
;
...
@@ -8,12 +9,14 @@ import com.example.SkuciSe.repository.KorisnikRepository;
...
@@ -8,12 +9,14 @@ import com.example.SkuciSe.repository.KorisnikRepository;
import
com.example.SkuciSe.repository.LokacijaRepository
;
import
com.example.SkuciSe.repository.LokacijaRepository
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.security.core.annotation.AuthenticationPrincipal
;
import
org.springframework.security.core.annotation.AuthenticationPrincipal
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.ui.Model
;
import
org.springframework.ui.Model
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.security.Principal
;
import
java.util.Base64
;
import
java.util.Base64
;
@Controller
@Controller
...
@@ -78,4 +81,9 @@ public class AppController
...
@@ -78,4 +81,9 @@ public class AppController
model
.
addAttribute
(
"loggedUser"
,
loggedUser
);
model
.
addAttribute
(
"loggedUser"
,
loggedUser
);
return
"portfolio"
;
return
"portfolio"
;
}
}
@GetMapping
(
"/chat"
)
public
String
getChat
(
Model
model
,
@AuthenticationPrincipal
KorisnikDetails
loggedUser
){
model
.
addAttribute
(
"loggedUser"
,
loggedUser
);
return
"chat"
;
}
}
}
SkuciSe/src/main/resources/static/js/socket.js
0 → 100644
View file @
5aa65891
var
stompClient
=
null
;
$
(
document
).
ready
(
function
()
{
console
.
log
(
"Index page is ready"
);
connect
();
$
(
"#send-private"
).
click
(
function
()
{
sendPrivateMessage
();
});
});
function
connect
()
{
var
socket
=
new
SockJS
(
'out-websocket'
);
stompClient
=
Stomp
.
over
(
socket
);
stompClient
.
connect
({},
function
(
frame
)
{
console
.
log
(
'Connected: '
+
frame
);
stompClient
.
subscribe
(
'/user/topic/private-messages'
,
function
(
message
)
{
showMessage
(
JSON
.
parse
(
message
.
body
).
content
);
});
});
}
function
showMessage
(
message
)
{
$
(
"#messages"
).
append
(
"<tr><td>"
+
message
+
"</td></tr>"
);
}
function
sendPrivateMessage
()
{
console
.
log
(
"sending private message"
);
stompClient
.
send
(
"/ws/private-message"
,
{},
JSON
.
stringify
({
'messageContent'
:
$
(
"#private-message"
).
val
()}));
}
\ No newline at end of file
SkuciSe/src/main/resources/templates/chat.html
0 → 100644
View file @
5aa65891
<!DOCTYPE html>
<html
lang=
"en"
xmlns=
"http://www.w3.org/1999/xhtml"
xmlns:th=
"http://www.thymeleaf.org"
>
<head>
<meta
charset=
"utf-8"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta
name=
"description"
content=
""
/>
<meta
name=
"author"
content=
""
/>
<title>
SkuciSe
</title>
<link
rel=
"shortcut icon"
type=
"image/x-icon"
href=
"/images/logo.ico"
/>
<link
href=
'https://fonts.googleapis.com/css?family=Jost'
rel=
'stylesheet'
>
<link
href=
"//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
rel=
"stylesheet"
id=
"bootstrap-css"
>
<link
rel=
"stylesheet"
href=
"https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>
<!-- Favicon-->
<link
rel=
"icon"
type=
"image/x-icon"
href=
"assets/favicon.ico"
/>
<!-- CSS only -->
<link
href=
"https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"
rel=
"stylesheet"
integrity=
"sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx"
crossorigin=
"anonymous"
>
<!-- Core theme CSS (includes Bootstrap)-->
<link
href=
"/css/style.css"
rel=
"stylesheet"
/>
</head>
<body
th:object=
"${loggedUser}"
>
<nav
class=
"navbar navbar-icon-top navbar-expand-lg p-3"
>
<a
class=
"navbar-brand"
href=
"#"
>
</a>
<button
class=
"navbar-toggler"
type=
"button"
data-toggle=
"collapse"
data-target=
"#navbarSupportedContent"
aria-controls=
"navbarSupportedContent"
aria-expanded=
"false"
aria-label=
"Toggle navigation"
>
<span
class=
"navbar-toggler-icon"
></span>
</button>
<div
class=
"collapse navbar-collapse"
id=
"navbarSupportedContent"
>
<ul
class=
"navbar-nav mr-auto"
>
<li
class=
"nav-item active"
>
<a
class=
"nav-link"
th:href=
"@{/index}"
>
<i
class=
"fa fa-home"
></i>
Pocetna
<span
class=
"sr-only"
>
(current)
</span>
</a>
</li>
<li
class=
"nav-item active"
>
<a
class=
"nav-link"
href=
"/lista-oglasa"
>
<i
class=
"fa fa-poll-h"
></i>
Lista oglasa
<span
class=
"sr-only"
>
(current)
</span>
</a>
</li>
<li
class=
"nav-item active"
>
<a
class=
"nav-link"
th:href=
"@{/onama}"
>
<i
class=
"fa-sharp fa-solid fa-address-card"
></i>
<p>
O nama
</p>
<span
class=
"sr-only"
>
(current)
</span>
</a>
</li>
<li
class=
"nav-item active"
>
<a
class=
"nav-link"
th:href=
"@{/portfolio}"
>
<i
class=
"fa-solid fa-briefcase"
></i>
<p>
Portfolio
</p>
<span
class=
"sr-only"
>
(current)
</span>
</a>
</li>
</ul>
<form
class=
"form-inline my-2 my-lg-0"
th:if=
"${loggedUser != null}"
>
<a
th:href=
"@{/novi-oglas}"
>
<button
type=
"button"
class=
"btn btn-primary btn-md mr-2"
><i
class=
"fa-solid fa-plus"
></i>
Postavite
novi oglas
</button>
</a>
</form>
<ul
class=
"navbar-nav"
>
<li
class=
"nav-item"
th:if=
"${loggedUser == null}"
>
<a
th:href=
"@{/login}"
><i
class=
"fas fa-sign-in-alt"
style=
"color:#495056"
></i><span
style=
"padding:10px;color:#495056"
>
Prijavi se
</span></a>
</li>
<li
class=
"nav-item"
th:if=
"${loggedUser == null}"
>
<a
th:href=
"@{/register}"
><i
class=
"fa-solid fa-circle-user"
style=
"color:#495056"
></i><span
style=
"padding:10px;color:#495056"
>
Registruj se
</span></a>
</li>
<li
th:if=
"${loggedUser != null}"
>
<div
class=
"dropdown mr-4"
>
<button
class=
"btn btn-secondary dropdown-toggle round"
type=
"button"
id=
"dropdownMenuButton"
data-toggle=
"dropdown"
aria-haspopup=
"true"
aria-expanded=
"false"
>
<i
class=
"fa-solid fa-circle-user"
></i><span
style=
"padding:10px;"
th:text=
"${loggedUser.getKorisnik().getIme()}"
></span>
</button>
<div
class=
"dropdown-menu"
aria-labelledby=
"dropdownMenuButton"
>
<a
class=
"dropdown-item"
th:href=
"@{/profile}"
>
Moj Profil
</a>
<a
class=
"dropdown-item"
th:href=
"@{/profile/moji-oglasi}"
>
Moji Oglasi
</a>
<a
class=
"dropdown-item"
th:href=
"@{/profile/moji-zahtevi}"
>
Moji Zahtevi
</a>
<a
th:if=
"${loggedUser.getKorisnik().getTipId() == 2}"
class=
"dropdown-item"
th:href=
"@{/lista-korisnika}"
>
Lista Profila
</a>
<form
th:action=
"@{/logout}"
method=
"post"
id=
"my_form"
class=
"dropdown-item"
>
<a
href=
"#"
onclick=
"document.getElementById('my_form').submit(); return false;"
><i
class=
"fas fa-sign-in-alt"
></i><span
style=
"padding:5px;"
>
Odjavi se
</span></a>
</form>
</div>
</div>
</li>
</ul>
</div>
</nav>
<section
class=
"features-icons bg-light text-center"
>
<div
class=
"row"
style=
"margin-top: 10px"
>
<div
class=
"col-md-12"
>
<form
class=
"form-inline"
>
<div
class=
"form-group"
>
<label
for=
"private-message"
>
Private Message
</label>
<input
type=
"text"
id=
"private-message"
class=
"form-control"
placeholder=
"Enter your message here..."
>
</div>
<button
id=
"send-private"
class=
"btn btn-default"
type=
"button"
>
Send Private Message
</button>
</form>
</div>
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<table
id=
"message-history"
class=
"table table-striped"
>
<thead>
<tr>
<th>
Messages
</th>
</tr>
</thead>
<tbody
id=
"messages"
>
</tbody>
</table>
</div>
</div>
</section>
<div
class=
"container-fluid pb-0 mb-0 justify-content-center text-light "
>
<footer>
<div
class=
"row my-5 justify-content-center py-5"
>
<div
class=
"col-11"
>
<div
class=
"row "
>
<div
class=
"col-xl-8 col-md-4 col-sm-4 col-12 my-auto mx-auto a"
><h3
class=
"text-muted mb-md-0 mb-5 bold-text"
>
SkuciSe
</h3></div>
<div
class=
"col-xl-2 col-md-4 col-sm-4 col-12"
><h6
class=
"mb-3 mb-lg-4 bold-text "
><b>
MENI
</b><i
class=
"fa-solid fa-bars"
></i></h6>
<ul
class=
"list-unstyled"
>
<li><a
href=
"/index"
style=
"text-decoration: none;color:#627482;"
onmouseover=
"this.style.color='#989c9e'"
onMouseOut=
"this.style.color='#627482'"
>
Pocetna
</a></li>
<li><a
href=
"/onama"
style=
"text-decoration: none;color:#627482;"
onmouseover=
"this.style.color='#989c9e'"
onMouseOut=
"this.style.color='#627482'"
>
O nama
</a></li>
<li><a
href=
"/portfolio"
style=
"text-decoration: none;color:#627482;"
onmouseover=
"this.style.color='#989c9e'"
onMouseOut=
"this.style.color='#627482'"
>
Portfolio
</a></li>
</ul>
</div>
<div
class=
"col-xl-2 col-md-4 col-sm-4 col-12"
><h6
class=
"mb-3 mb-lg-4 text-muted bold-text mt-sm-0 mt-5"
><b>
ADRESA
</b><i
class=
"fa-solid fa-map-pin"
></i></h6>
<p><a
href=
"http://maps.google.com/maps?daddr=Radoja Domanovića 12 Kragujevac&"
style=
"text-decoration: none;color:#627482;"
onmouseover=
"this.style.color='#989c9e'"
onMouseOut=
"this.style.color='#627482'"
>
Radoja Domanovića 12 Kragujevac 34000
</a></p>
</div>
<div
class=
"row "
>
<div
class=
"col-xl-8 col-md-4 col-sm-4 col-auto my-md-0 mt-5 order-sm-1 order-3 align-self-end"
><p
class=
"social text-muted mb-0 pb-0 bold-text"
><span
class=
"mx-2"
><i
class=
"fa fa-facebook"
aria-hidden=
"true"
></i></span>
<span
class=
"mx-2"
><i
class=
"fa fa-linkedin-square"
aria-hidden=
"true"
></i></span>
<span
class=
"mx-2"
><i
class=
"fa fa-twitter"
aria-hidden=
"true"
></i></span>
<span
class=
"mx-2"
><i
class=
"fa fa-instagram"
aria-hidden=
"true"
></i></span></p><small
class=
"rights"
><span>
®
</span>
SkuciSe. Sva prava zadrzana.
</small></div>
<div
class=
"col-xl-2 col-md-4 col-sm-4 col-auto order-1 align-self-end "
><h6
class=
"mt-55 mt-2 text-muted bold-text"
><b>
Korisnicki centar
</b><i
class=
"fa-solid fa-phone"
></i></h6><small>
<span><i
class=
"fa fa-envelope"
aria-hidden=
"true"
></i></span><a
href=
"mailto:someone@yoursite.com"
style=
"text-decoration: none;color:#627482;"
onmouseover=
"this.style.color='#989c9e'"
onMouseOut=
"this.style.color='#627482'"
>
skucise@gmail.com
</a>
</small></div>
</div>
</div>
</div></div>
</footer>
</div>
<!-- JavaScript Bundle with Popper -->
<script
src=
"//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"
></script>
<script
src=
"//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"
></script>
<script
src=
"https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"
></script>
<script
src=
"https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"
></script>
<script
src=
"https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity=
"sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin=
"anonymous"
></script>
<script
src=
"https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity=
"sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin=
"anonymous"
></script>
<script
src=
"https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity=
"sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin=
"anonymous"
></script>
<script
src=
"https://kit.fontawesome.com/51d1fadef3.js"
crossorigin=
"anonymous"
></script>
<script
src=
"/js/socket.js"
></script>
</body>
</html>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment