├── .gitignore ├── README.md ├── composer.json ├── composer.lock └── src ├── chat-server.php ├── config ├── config.DEV.php └── config.PROD.php ├── core ├── AESCipher.php ├── App.php ├── Auth.php ├── Config.php ├── Console.php ├── Database.php └── Environment.php └── models ├── ChatRoom.php └── Client.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.phar 3 | src/config/config.DEV.php 4 | src/config/config.PROD.php -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat Socket Server 2 | 3 | A simple PHP chat socket server made with [Ratchet](https://github.com/ratchetphp/Ratchet), for your website. 4 | 5 | ## How does it work? 6 | 7 | 1. A user visits the conversation page on your website. 8 | 2. Upon loading, the client requests encrypted authentication details from your website for the socket server to verify the connection. 9 | 3. A web socket is created on the client side, which sends a connection request to the server, including the authentication details as a query parameter. 10 | 4. The socket server decrypts and validates the authentication details. 11 | 5. If valid, the server creates a new chat room (if it doesn't already exist) using the chat ID from the authentication details, and adds the user with their user ID and connection to the room. 12 | 6. When a second participant joins the conversation, they request a connection with their authentication details. If valid, they are connected to the existing room using their user ID and connection. 13 | 7. When a participant sends a message, the socket server identifies the room associated with the authenticated connection, saves the message to your website's database, and then forwards the message to the other participant in the room. 14 | 8. Upon receiving the message from the socket server, the client-side socket executes a method to display the live message in the conversation. 15 | 16 | ## TODO 17 | 18 | - Add code examples to README.md 19 | - Add deployment guide to README.md 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoload": { 3 | "psr-4": { 4 | "Core\\": "src/core", 5 | "Models\\": "src/models" 6 | } 7 | }, 8 | "require": { 9 | "cboden/ratchet": "^0.4.4" 10 | } 11 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "65c9a8f87141c77ac44bfffa60df8163", 8 | "packages": [ 9 | { 10 | "name": "cboden/ratchet", 11 | "version": "v0.4.4", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/ratchetphp/Ratchet.git", 15 | "reference": "5012dc954541b40c5599d286fd40653f5716a38f" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/5012dc954541b40c5599d286fd40653f5716a38f", 20 | "reference": "5012dc954541b40c5599d286fd40653f5716a38f", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "guzzlehttp/psr7": "^1.7|^2.0", 25 | "php": ">=5.4.2", 26 | "ratchet/rfc6455": "^0.3.1", 27 | "react/event-loop": ">=0.4", 28 | "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5", 29 | "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0", 30 | "symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "~4.8" 34 | }, 35 | "type": "library", 36 | "autoload": { 37 | "psr-4": { 38 | "Ratchet\\": "src/Ratchet" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Chris Boden", 48 | "email": "cboden@gmail.com", 49 | "role": "Developer" 50 | }, 51 | { 52 | "name": "Matt Bonneau", 53 | "role": "Developer" 54 | } 55 | ], 56 | "description": "PHP WebSocket library", 57 | "homepage": "http://socketo.me", 58 | "keywords": [ 59 | "Ratchet", 60 | "WebSockets", 61 | "server", 62 | "sockets", 63 | "websocket" 64 | ], 65 | "support": { 66 | "chat": "https://gitter.im/reactphp/reactphp", 67 | "issues": "https://github.com/ratchetphp/Ratchet/issues", 68 | "source": "https://github.com/ratchetphp/Ratchet/tree/v0.4.4" 69 | }, 70 | "time": "2021-12-14T00:20:41+00:00" 71 | }, 72 | { 73 | "name": "evenement/evenement", 74 | "version": "v3.0.1", 75 | "source": { 76 | "type": "git", 77 | "url": "https://github.com/igorw/evenement.git", 78 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" 79 | }, 80 | "dist": { 81 | "type": "zip", 82 | "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 83 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 84 | "shasum": "" 85 | }, 86 | "require": { 87 | "php": ">=7.0" 88 | }, 89 | "require-dev": { 90 | "phpunit/phpunit": "^6.0" 91 | }, 92 | "type": "library", 93 | "autoload": { 94 | "psr-0": { 95 | "Evenement": "src" 96 | } 97 | }, 98 | "notification-url": "https://packagist.org/downloads/", 99 | "license": [ 100 | "MIT" 101 | ], 102 | "authors": [ 103 | { 104 | "name": "Igor Wiedler", 105 | "email": "igor@wiedler.ch" 106 | } 107 | ], 108 | "description": "Événement is a very simple event dispatching library for PHP", 109 | "keywords": [ 110 | "event-dispatcher", 111 | "event-emitter" 112 | ], 113 | "support": { 114 | "issues": "https://github.com/igorw/evenement/issues", 115 | "source": "https://github.com/igorw/evenement/tree/master" 116 | }, 117 | "time": "2017-07-23T21:35:13+00:00" 118 | }, 119 | { 120 | "name": "guzzlehttp/psr7", 121 | "version": "2.4.3", 122 | "source": { 123 | "type": "git", 124 | "url": "https://github.com/guzzle/psr7.git", 125 | "reference": "67c26b443f348a51926030c83481b85718457d3d" 126 | }, 127 | "dist": { 128 | "type": "zip", 129 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", 130 | "reference": "67c26b443f348a51926030c83481b85718457d3d", 131 | "shasum": "" 132 | }, 133 | "require": { 134 | "php": "^7.2.5 || ^8.0", 135 | "psr/http-factory": "^1.0", 136 | "psr/http-message": "^1.0", 137 | "ralouphie/getallheaders": "^3.0" 138 | }, 139 | "provide": { 140 | "psr/http-factory-implementation": "1.0", 141 | "psr/http-message-implementation": "1.0" 142 | }, 143 | "require-dev": { 144 | "bamarni/composer-bin-plugin": "^1.8.1", 145 | "http-interop/http-factory-tests": "^0.9", 146 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 147 | }, 148 | "suggest": { 149 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 150 | }, 151 | "type": "library", 152 | "extra": { 153 | "bamarni-bin": { 154 | "bin-links": true, 155 | "forward-command": false 156 | }, 157 | "branch-alias": { 158 | "dev-master": "2.4-dev" 159 | } 160 | }, 161 | "autoload": { 162 | "psr-4": { 163 | "GuzzleHttp\\Psr7\\": "src/" 164 | } 165 | }, 166 | "notification-url": "https://packagist.org/downloads/", 167 | "license": [ 168 | "MIT" 169 | ], 170 | "authors": [ 171 | { 172 | "name": "Graham Campbell", 173 | "email": "hello@gjcampbell.co.uk", 174 | "homepage": "https://github.com/GrahamCampbell" 175 | }, 176 | { 177 | "name": "Michael Dowling", 178 | "email": "mtdowling@gmail.com", 179 | "homepage": "https://github.com/mtdowling" 180 | }, 181 | { 182 | "name": "George Mponos", 183 | "email": "gmponos@gmail.com", 184 | "homepage": "https://github.com/gmponos" 185 | }, 186 | { 187 | "name": "Tobias Nyholm", 188 | "email": "tobias.nyholm@gmail.com", 189 | "homepage": "https://github.com/Nyholm" 190 | }, 191 | { 192 | "name": "Márk Sági-Kazár", 193 | "email": "mark.sagikazar@gmail.com", 194 | "homepage": "https://github.com/sagikazarmark" 195 | }, 196 | { 197 | "name": "Tobias Schultze", 198 | "email": "webmaster@tubo-world.de", 199 | "homepage": "https://github.com/Tobion" 200 | }, 201 | { 202 | "name": "Márk Sági-Kazár", 203 | "email": "mark.sagikazar@gmail.com", 204 | "homepage": "https://sagikazarmark.hu" 205 | } 206 | ], 207 | "description": "PSR-7 message implementation that also provides common utility methods", 208 | "keywords": [ 209 | "http", 210 | "message", 211 | "psr-7", 212 | "request", 213 | "response", 214 | "stream", 215 | "uri", 216 | "url" 217 | ], 218 | "support": { 219 | "issues": "https://github.com/guzzle/psr7/issues", 220 | "source": "https://github.com/guzzle/psr7/tree/2.4.3" 221 | }, 222 | "funding": [ 223 | { 224 | "url": "https://github.com/GrahamCampbell", 225 | "type": "github" 226 | }, 227 | { 228 | "url": "https://github.com/Nyholm", 229 | "type": "github" 230 | }, 231 | { 232 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 233 | "type": "tidelift" 234 | } 235 | ], 236 | "time": "2022-10-26T14:07:24+00:00" 237 | }, 238 | { 239 | "name": "psr/http-factory", 240 | "version": "1.0.1", 241 | "source": { 242 | "type": "git", 243 | "url": "https://github.com/php-fig/http-factory.git", 244 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" 245 | }, 246 | "dist": { 247 | "type": "zip", 248 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 249 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 250 | "shasum": "" 251 | }, 252 | "require": { 253 | "php": ">=7.0.0", 254 | "psr/http-message": "^1.0" 255 | }, 256 | "type": "library", 257 | "extra": { 258 | "branch-alias": { 259 | "dev-master": "1.0.x-dev" 260 | } 261 | }, 262 | "autoload": { 263 | "psr-4": { 264 | "Psr\\Http\\Message\\": "src/" 265 | } 266 | }, 267 | "notification-url": "https://packagist.org/downloads/", 268 | "license": [ 269 | "MIT" 270 | ], 271 | "authors": [ 272 | { 273 | "name": "PHP-FIG", 274 | "homepage": "http://www.php-fig.org/" 275 | } 276 | ], 277 | "description": "Common interfaces for PSR-7 HTTP message factories", 278 | "keywords": [ 279 | "factory", 280 | "http", 281 | "message", 282 | "psr", 283 | "psr-17", 284 | "psr-7", 285 | "request", 286 | "response" 287 | ], 288 | "support": { 289 | "source": "https://github.com/php-fig/http-factory/tree/master" 290 | }, 291 | "time": "2019-04-30T12:38:16+00:00" 292 | }, 293 | { 294 | "name": "psr/http-message", 295 | "version": "1.0.1", 296 | "source": { 297 | "type": "git", 298 | "url": "https://github.com/php-fig/http-message.git", 299 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 300 | }, 301 | "dist": { 302 | "type": "zip", 303 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 304 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 305 | "shasum": "" 306 | }, 307 | "require": { 308 | "php": ">=5.3.0" 309 | }, 310 | "type": "library", 311 | "extra": { 312 | "branch-alias": { 313 | "dev-master": "1.0.x-dev" 314 | } 315 | }, 316 | "autoload": { 317 | "psr-4": { 318 | "Psr\\Http\\Message\\": "src/" 319 | } 320 | }, 321 | "notification-url": "https://packagist.org/downloads/", 322 | "license": [ 323 | "MIT" 324 | ], 325 | "authors": [ 326 | { 327 | "name": "PHP-FIG", 328 | "homepage": "http://www.php-fig.org/" 329 | } 330 | ], 331 | "description": "Common interface for HTTP messages", 332 | "homepage": "https://github.com/php-fig/http-message", 333 | "keywords": [ 334 | "http", 335 | "http-message", 336 | "psr", 337 | "psr-7", 338 | "request", 339 | "response" 340 | ], 341 | "support": { 342 | "source": "https://github.com/php-fig/http-message/tree/master" 343 | }, 344 | "time": "2016-08-06T14:39:51+00:00" 345 | }, 346 | { 347 | "name": "ralouphie/getallheaders", 348 | "version": "3.0.3", 349 | "source": { 350 | "type": "git", 351 | "url": "https://github.com/ralouphie/getallheaders.git", 352 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 353 | }, 354 | "dist": { 355 | "type": "zip", 356 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 357 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 358 | "shasum": "" 359 | }, 360 | "require": { 361 | "php": ">=5.6" 362 | }, 363 | "require-dev": { 364 | "php-coveralls/php-coveralls": "^2.1", 365 | "phpunit/phpunit": "^5 || ^6.5" 366 | }, 367 | "type": "library", 368 | "autoload": { 369 | "files": [ 370 | "src/getallheaders.php" 371 | ] 372 | }, 373 | "notification-url": "https://packagist.org/downloads/", 374 | "license": [ 375 | "MIT" 376 | ], 377 | "authors": [ 378 | { 379 | "name": "Ralph Khattar", 380 | "email": "ralph.khattar@gmail.com" 381 | } 382 | ], 383 | "description": "A polyfill for getallheaders.", 384 | "support": { 385 | "issues": "https://github.com/ralouphie/getallheaders/issues", 386 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 387 | }, 388 | "time": "2019-03-08T08:55:37+00:00" 389 | }, 390 | { 391 | "name": "ratchet/rfc6455", 392 | "version": "v0.3.1", 393 | "source": { 394 | "type": "git", 395 | "url": "https://github.com/ratchetphp/RFC6455.git", 396 | "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb" 397 | }, 398 | "dist": { 399 | "type": "zip", 400 | "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/7c964514e93456a52a99a20fcfa0de242a43ccdb", 401 | "reference": "7c964514e93456a52a99a20fcfa0de242a43ccdb", 402 | "shasum": "" 403 | }, 404 | "require": { 405 | "guzzlehttp/psr7": "^2 || ^1.7", 406 | "php": ">=5.4.2" 407 | }, 408 | "require-dev": { 409 | "phpunit/phpunit": "^5.7", 410 | "react/socket": "^1.3" 411 | }, 412 | "type": "library", 413 | "autoload": { 414 | "psr-4": { 415 | "Ratchet\\RFC6455\\": "src" 416 | } 417 | }, 418 | "notification-url": "https://packagist.org/downloads/", 419 | "license": [ 420 | "MIT" 421 | ], 422 | "authors": [ 423 | { 424 | "name": "Chris Boden", 425 | "email": "cboden@gmail.com", 426 | "role": "Developer" 427 | }, 428 | { 429 | "name": "Matt Bonneau", 430 | "role": "Developer" 431 | } 432 | ], 433 | "description": "RFC6455 WebSocket protocol handler", 434 | "homepage": "http://socketo.me", 435 | "keywords": [ 436 | "WebSockets", 437 | "rfc6455", 438 | "websocket" 439 | ], 440 | "support": { 441 | "chat": "https://gitter.im/reactphp/reactphp", 442 | "issues": "https://github.com/ratchetphp/RFC6455/issues", 443 | "source": "https://github.com/ratchetphp/RFC6455/tree/v0.3.1" 444 | }, 445 | "time": "2021-12-09T23:20:49+00:00" 446 | }, 447 | { 448 | "name": "react/cache", 449 | "version": "v1.2.0", 450 | "source": { 451 | "type": "git", 452 | "url": "https://github.com/reactphp/cache.git", 453 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" 454 | }, 455 | "dist": { 456 | "type": "zip", 457 | "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", 458 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", 459 | "shasum": "" 460 | }, 461 | "require": { 462 | "php": ">=5.3.0", 463 | "react/promise": "^3.0 || ^2.0 || ^1.1" 464 | }, 465 | "require-dev": { 466 | "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" 467 | }, 468 | "type": "library", 469 | "autoload": { 470 | "psr-4": { 471 | "React\\Cache\\": "src/" 472 | } 473 | }, 474 | "notification-url": "https://packagist.org/downloads/", 475 | "license": [ 476 | "MIT" 477 | ], 478 | "authors": [ 479 | { 480 | "name": "Christian Lück", 481 | "email": "christian@clue.engineering", 482 | "homepage": "https://clue.engineering/" 483 | }, 484 | { 485 | "name": "Cees-Jan Kiewiet", 486 | "email": "reactphp@ceesjankiewiet.nl", 487 | "homepage": "https://wyrihaximus.net/" 488 | }, 489 | { 490 | "name": "Jan Sorgalla", 491 | "email": "jsorgalla@gmail.com", 492 | "homepage": "https://sorgalla.com/" 493 | }, 494 | { 495 | "name": "Chris Boden", 496 | "email": "cboden@gmail.com", 497 | "homepage": "https://cboden.dev/" 498 | } 499 | ], 500 | "description": "Async, Promise-based cache interface for ReactPHP", 501 | "keywords": [ 502 | "cache", 503 | "caching", 504 | "promise", 505 | "reactphp" 506 | ], 507 | "support": { 508 | "issues": "https://github.com/reactphp/cache/issues", 509 | "source": "https://github.com/reactphp/cache/tree/v1.2.0" 510 | }, 511 | "funding": [ 512 | { 513 | "url": "https://opencollective.com/reactphp", 514 | "type": "open_collective" 515 | } 516 | ], 517 | "time": "2022-11-30T15:59:55+00:00" 518 | }, 519 | { 520 | "name": "react/dns", 521 | "version": "v1.10.0", 522 | "source": { 523 | "type": "git", 524 | "url": "https://github.com/reactphp/dns.git", 525 | "reference": "a5427e7dfa47713e438016905605819d101f238c" 526 | }, 527 | "dist": { 528 | "type": "zip", 529 | "url": "https://api.github.com/repos/reactphp/dns/zipball/a5427e7dfa47713e438016905605819d101f238c", 530 | "reference": "a5427e7dfa47713e438016905605819d101f238c", 531 | "shasum": "" 532 | }, 533 | "require": { 534 | "php": ">=5.3.0", 535 | "react/cache": "^1.0 || ^0.6 || ^0.5", 536 | "react/event-loop": "^1.2", 537 | "react/promise": "^3.0 || ^2.7 || ^1.2.1", 538 | "react/promise-timer": "^1.9" 539 | }, 540 | "require-dev": { 541 | "phpunit/phpunit": "^9.3 || ^4.8.35", 542 | "react/async": "^4 || ^3 || ^2" 543 | }, 544 | "type": "library", 545 | "autoload": { 546 | "psr-4": { 547 | "React\\Dns\\": "src" 548 | } 549 | }, 550 | "notification-url": "https://packagist.org/downloads/", 551 | "license": [ 552 | "MIT" 553 | ], 554 | "authors": [ 555 | { 556 | "name": "Christian Lück", 557 | "email": "christian@clue.engineering", 558 | "homepage": "https://clue.engineering/" 559 | }, 560 | { 561 | "name": "Cees-Jan Kiewiet", 562 | "email": "reactphp@ceesjankiewiet.nl", 563 | "homepage": "https://wyrihaximus.net/" 564 | }, 565 | { 566 | "name": "Jan Sorgalla", 567 | "email": "jsorgalla@gmail.com", 568 | "homepage": "https://sorgalla.com/" 569 | }, 570 | { 571 | "name": "Chris Boden", 572 | "email": "cboden@gmail.com", 573 | "homepage": "https://cboden.dev/" 574 | } 575 | ], 576 | "description": "Async DNS resolver for ReactPHP", 577 | "keywords": [ 578 | "async", 579 | "dns", 580 | "dns-resolver", 581 | "reactphp" 582 | ], 583 | "support": { 584 | "issues": "https://github.com/reactphp/dns/issues", 585 | "source": "https://github.com/reactphp/dns/tree/v1.10.0" 586 | }, 587 | "funding": [ 588 | { 589 | "url": "https://github.com/WyriHaximus", 590 | "type": "github" 591 | }, 592 | { 593 | "url": "https://github.com/clue", 594 | "type": "github" 595 | } 596 | ], 597 | "time": "2022-09-08T12:22:46+00:00" 598 | }, 599 | { 600 | "name": "react/event-loop", 601 | "version": "v1.3.0", 602 | "source": { 603 | "type": "git", 604 | "url": "https://github.com/reactphp/event-loop.git", 605 | "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137" 606 | }, 607 | "dist": { 608 | "type": "zip", 609 | "url": "https://api.github.com/repos/reactphp/event-loop/zipball/187fb56f46d424afb6ec4ad089269c72eec2e137", 610 | "reference": "187fb56f46d424afb6ec4ad089269c72eec2e137", 611 | "shasum": "" 612 | }, 613 | "require": { 614 | "php": ">=5.3.0" 615 | }, 616 | "require-dev": { 617 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 618 | }, 619 | "suggest": { 620 | "ext-event": "~1.0 for ExtEventLoop", 621 | "ext-pcntl": "For signal handling support when using the StreamSelectLoop", 622 | "ext-uv": "* for ExtUvLoop" 623 | }, 624 | "type": "library", 625 | "autoload": { 626 | "psr-4": { 627 | "React\\EventLoop\\": "src" 628 | } 629 | }, 630 | "notification-url": "https://packagist.org/downloads/", 631 | "license": [ 632 | "MIT" 633 | ], 634 | "authors": [ 635 | { 636 | "name": "Christian Lück", 637 | "email": "christian@clue.engineering", 638 | "homepage": "https://clue.engineering/" 639 | }, 640 | { 641 | "name": "Cees-Jan Kiewiet", 642 | "email": "reactphp@ceesjankiewiet.nl", 643 | "homepage": "https://wyrihaximus.net/" 644 | }, 645 | { 646 | "name": "Jan Sorgalla", 647 | "email": "jsorgalla@gmail.com", 648 | "homepage": "https://sorgalla.com/" 649 | }, 650 | { 651 | "name": "Chris Boden", 652 | "email": "cboden@gmail.com", 653 | "homepage": "https://cboden.dev/" 654 | } 655 | ], 656 | "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 657 | "keywords": [ 658 | "asynchronous", 659 | "event-loop" 660 | ], 661 | "support": { 662 | "issues": "https://github.com/reactphp/event-loop/issues", 663 | "source": "https://github.com/reactphp/event-loop/tree/v1.3.0" 664 | }, 665 | "funding": [ 666 | { 667 | "url": "https://github.com/WyriHaximus", 668 | "type": "github" 669 | }, 670 | { 671 | "url": "https://github.com/clue", 672 | "type": "github" 673 | } 674 | ], 675 | "time": "2022-03-17T11:10:22+00:00" 676 | }, 677 | { 678 | "name": "react/promise", 679 | "version": "v2.9.0", 680 | "source": { 681 | "type": "git", 682 | "url": "https://github.com/reactphp/promise.git", 683 | "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" 684 | }, 685 | "dist": { 686 | "type": "zip", 687 | "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", 688 | "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", 689 | "shasum": "" 690 | }, 691 | "require": { 692 | "php": ">=5.4.0" 693 | }, 694 | "require-dev": { 695 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" 696 | }, 697 | "type": "library", 698 | "autoload": { 699 | "files": [ 700 | "src/functions_include.php" 701 | ], 702 | "psr-4": { 703 | "React\\Promise\\": "src/" 704 | } 705 | }, 706 | "notification-url": "https://packagist.org/downloads/", 707 | "license": [ 708 | "MIT" 709 | ], 710 | "authors": [ 711 | { 712 | "name": "Jan Sorgalla", 713 | "email": "jsorgalla@gmail.com", 714 | "homepage": "https://sorgalla.com/" 715 | }, 716 | { 717 | "name": "Christian Lück", 718 | "email": "christian@clue.engineering", 719 | "homepage": "https://clue.engineering/" 720 | }, 721 | { 722 | "name": "Cees-Jan Kiewiet", 723 | "email": "reactphp@ceesjankiewiet.nl", 724 | "homepage": "https://wyrihaximus.net/" 725 | }, 726 | { 727 | "name": "Chris Boden", 728 | "email": "cboden@gmail.com", 729 | "homepage": "https://cboden.dev/" 730 | } 731 | ], 732 | "description": "A lightweight implementation of CommonJS Promises/A for PHP", 733 | "keywords": [ 734 | "promise", 735 | "promises" 736 | ], 737 | "support": { 738 | "issues": "https://github.com/reactphp/promise/issues", 739 | "source": "https://github.com/reactphp/promise/tree/v2.9.0" 740 | }, 741 | "funding": [ 742 | { 743 | "url": "https://github.com/WyriHaximus", 744 | "type": "github" 745 | }, 746 | { 747 | "url": "https://github.com/clue", 748 | "type": "github" 749 | } 750 | ], 751 | "time": "2022-02-11T10:27:51+00:00" 752 | }, 753 | { 754 | "name": "react/promise-timer", 755 | "version": "v1.9.0", 756 | "source": { 757 | "type": "git", 758 | "url": "https://github.com/reactphp/promise-timer.git", 759 | "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495" 760 | }, 761 | "dist": { 762 | "type": "zip", 763 | "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", 764 | "reference": "aa7a73c74b8d8c0f622f5982ff7b0351bc29e495", 765 | "shasum": "" 766 | }, 767 | "require": { 768 | "php": ">=5.3", 769 | "react/event-loop": "^1.2", 770 | "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" 771 | }, 772 | "require-dev": { 773 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 774 | }, 775 | "type": "library", 776 | "autoload": { 777 | "files": [ 778 | "src/functions_include.php" 779 | ], 780 | "psr-4": { 781 | "React\\Promise\\Timer\\": "src/" 782 | } 783 | }, 784 | "notification-url": "https://packagist.org/downloads/", 785 | "license": [ 786 | "MIT" 787 | ], 788 | "authors": [ 789 | { 790 | "name": "Christian Lück", 791 | "email": "christian@clue.engineering", 792 | "homepage": "https://clue.engineering/" 793 | }, 794 | { 795 | "name": "Cees-Jan Kiewiet", 796 | "email": "reactphp@ceesjankiewiet.nl", 797 | "homepage": "https://wyrihaximus.net/" 798 | }, 799 | { 800 | "name": "Jan Sorgalla", 801 | "email": "jsorgalla@gmail.com", 802 | "homepage": "https://sorgalla.com/" 803 | }, 804 | { 805 | "name": "Chris Boden", 806 | "email": "cboden@gmail.com", 807 | "homepage": "https://cboden.dev/" 808 | } 809 | ], 810 | "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", 811 | "homepage": "https://github.com/reactphp/promise-timer", 812 | "keywords": [ 813 | "async", 814 | "event-loop", 815 | "promise", 816 | "reactphp", 817 | "timeout", 818 | "timer" 819 | ], 820 | "support": { 821 | "issues": "https://github.com/reactphp/promise-timer/issues", 822 | "source": "https://github.com/reactphp/promise-timer/tree/v1.9.0" 823 | }, 824 | "funding": [ 825 | { 826 | "url": "https://github.com/WyriHaximus", 827 | "type": "github" 828 | }, 829 | { 830 | "url": "https://github.com/clue", 831 | "type": "github" 832 | } 833 | ], 834 | "time": "2022-06-13T13:41:03+00:00" 835 | }, 836 | { 837 | "name": "react/socket", 838 | "version": "v1.12.0", 839 | "source": { 840 | "type": "git", 841 | "url": "https://github.com/reactphp/socket.git", 842 | "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b" 843 | }, 844 | "dist": { 845 | "type": "zip", 846 | "url": "https://api.github.com/repos/reactphp/socket/zipball/81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", 847 | "reference": "81e1b4d7f5450ebd8d2e9a95bb008bb15ca95a7b", 848 | "shasum": "" 849 | }, 850 | "require": { 851 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 852 | "php": ">=5.3.0", 853 | "react/dns": "^1.8", 854 | "react/event-loop": "^1.2", 855 | "react/promise": "^3 || ^2.6 || ^1.2.1", 856 | "react/promise-timer": "^1.9", 857 | "react/stream": "^1.2" 858 | }, 859 | "require-dev": { 860 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", 861 | "react/async": "^4 || ^3 || ^2", 862 | "react/promise-stream": "^1.4" 863 | }, 864 | "type": "library", 865 | "autoload": { 866 | "psr-4": { 867 | "React\\Socket\\": "src" 868 | } 869 | }, 870 | "notification-url": "https://packagist.org/downloads/", 871 | "license": [ 872 | "MIT" 873 | ], 874 | "authors": [ 875 | { 876 | "name": "Christian Lück", 877 | "email": "christian@clue.engineering", 878 | "homepage": "https://clue.engineering/" 879 | }, 880 | { 881 | "name": "Cees-Jan Kiewiet", 882 | "email": "reactphp@ceesjankiewiet.nl", 883 | "homepage": "https://wyrihaximus.net/" 884 | }, 885 | { 886 | "name": "Jan Sorgalla", 887 | "email": "jsorgalla@gmail.com", 888 | "homepage": "https://sorgalla.com/" 889 | }, 890 | { 891 | "name": "Chris Boden", 892 | "email": "cboden@gmail.com", 893 | "homepage": "https://cboden.dev/" 894 | } 895 | ], 896 | "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", 897 | "keywords": [ 898 | "Connection", 899 | "Socket", 900 | "async", 901 | "reactphp", 902 | "stream" 903 | ], 904 | "support": { 905 | "issues": "https://github.com/reactphp/socket/issues", 906 | "source": "https://github.com/reactphp/socket/tree/v1.12.0" 907 | }, 908 | "funding": [ 909 | { 910 | "url": "https://github.com/WyriHaximus", 911 | "type": "github" 912 | }, 913 | { 914 | "url": "https://github.com/clue", 915 | "type": "github" 916 | } 917 | ], 918 | "time": "2022-08-25T12:32:25+00:00" 919 | }, 920 | { 921 | "name": "react/stream", 922 | "version": "v1.2.0", 923 | "source": { 924 | "type": "git", 925 | "url": "https://github.com/reactphp/stream.git", 926 | "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9" 927 | }, 928 | "dist": { 929 | "type": "zip", 930 | "url": "https://api.github.com/repos/reactphp/stream/zipball/7a423506ee1903e89f1e08ec5f0ed430ff784ae9", 931 | "reference": "7a423506ee1903e89f1e08ec5f0ed430ff784ae9", 932 | "shasum": "" 933 | }, 934 | "require": { 935 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 936 | "php": ">=5.3.8", 937 | "react/event-loop": "^1.2" 938 | }, 939 | "require-dev": { 940 | "clue/stream-filter": "~1.2", 941 | "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" 942 | }, 943 | "type": "library", 944 | "autoload": { 945 | "psr-4": { 946 | "React\\Stream\\": "src" 947 | } 948 | }, 949 | "notification-url": "https://packagist.org/downloads/", 950 | "license": [ 951 | "MIT" 952 | ], 953 | "authors": [ 954 | { 955 | "name": "Christian Lück", 956 | "email": "christian@clue.engineering", 957 | "homepage": "https://clue.engineering/" 958 | }, 959 | { 960 | "name": "Cees-Jan Kiewiet", 961 | "email": "reactphp@ceesjankiewiet.nl", 962 | "homepage": "https://wyrihaximus.net/" 963 | }, 964 | { 965 | "name": "Jan Sorgalla", 966 | "email": "jsorgalla@gmail.com", 967 | "homepage": "https://sorgalla.com/" 968 | }, 969 | { 970 | "name": "Chris Boden", 971 | "email": "cboden@gmail.com", 972 | "homepage": "https://cboden.dev/" 973 | } 974 | ], 975 | "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", 976 | "keywords": [ 977 | "event-driven", 978 | "io", 979 | "non-blocking", 980 | "pipe", 981 | "reactphp", 982 | "readable", 983 | "stream", 984 | "writable" 985 | ], 986 | "support": { 987 | "issues": "https://github.com/reactphp/stream/issues", 988 | "source": "https://github.com/reactphp/stream/tree/v1.2.0" 989 | }, 990 | "funding": [ 991 | { 992 | "url": "https://github.com/WyriHaximus", 993 | "type": "github" 994 | }, 995 | { 996 | "url": "https://github.com/clue", 997 | "type": "github" 998 | } 999 | ], 1000 | "time": "2021-07-11T12:37:55+00:00" 1001 | }, 1002 | { 1003 | "name": "symfony/deprecation-contracts", 1004 | "version": "v3.2.0", 1005 | "source": { 1006 | "type": "git", 1007 | "url": "https://github.com/symfony/deprecation-contracts.git", 1008 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" 1009 | }, 1010 | "dist": { 1011 | "type": "zip", 1012 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", 1013 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", 1014 | "shasum": "" 1015 | }, 1016 | "require": { 1017 | "php": ">=8.1" 1018 | }, 1019 | "type": "library", 1020 | "extra": { 1021 | "branch-alias": { 1022 | "dev-main": "3.3-dev" 1023 | }, 1024 | "thanks": { 1025 | "name": "symfony/contracts", 1026 | "url": "https://github.com/symfony/contracts" 1027 | } 1028 | }, 1029 | "autoload": { 1030 | "files": [ 1031 | "function.php" 1032 | ] 1033 | }, 1034 | "notification-url": "https://packagist.org/downloads/", 1035 | "license": [ 1036 | "MIT" 1037 | ], 1038 | "authors": [ 1039 | { 1040 | "name": "Nicolas Grekas", 1041 | "email": "p@tchwork.com" 1042 | }, 1043 | { 1044 | "name": "Symfony Community", 1045 | "homepage": "https://symfony.com/contributors" 1046 | } 1047 | ], 1048 | "description": "A generic function and convention to trigger deprecation notices", 1049 | "homepage": "https://symfony.com", 1050 | "support": { 1051 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" 1052 | }, 1053 | "funding": [ 1054 | { 1055 | "url": "https://symfony.com/sponsor", 1056 | "type": "custom" 1057 | }, 1058 | { 1059 | "url": "https://github.com/fabpot", 1060 | "type": "github" 1061 | }, 1062 | { 1063 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1064 | "type": "tidelift" 1065 | } 1066 | ], 1067 | "time": "2022-11-25T10:21:52+00:00" 1068 | }, 1069 | { 1070 | "name": "symfony/http-foundation", 1071 | "version": "v6.2.6", 1072 | "source": { 1073 | "type": "git", 1074 | "url": "https://github.com/symfony/http-foundation.git", 1075 | "reference": "e8dd1f502bc2b3371d05092aa233b064b03ce7ed" 1076 | }, 1077 | "dist": { 1078 | "type": "zip", 1079 | "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8dd1f502bc2b3371d05092aa233b064b03ce7ed", 1080 | "reference": "e8dd1f502bc2b3371d05092aa233b064b03ce7ed", 1081 | "shasum": "" 1082 | }, 1083 | "require": { 1084 | "php": ">=8.1", 1085 | "symfony/deprecation-contracts": "^2.1|^3", 1086 | "symfony/polyfill-mbstring": "~1.1" 1087 | }, 1088 | "conflict": { 1089 | "symfony/cache": "<6.2" 1090 | }, 1091 | "require-dev": { 1092 | "predis/predis": "~1.0", 1093 | "symfony/cache": "^5.4|^6.0", 1094 | "symfony/dependency-injection": "^5.4|^6.0", 1095 | "symfony/expression-language": "^5.4|^6.0", 1096 | "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", 1097 | "symfony/mime": "^5.4|^6.0", 1098 | "symfony/rate-limiter": "^5.2|^6.0" 1099 | }, 1100 | "suggest": { 1101 | "symfony/mime": "To use the file extension guesser" 1102 | }, 1103 | "type": "library", 1104 | "autoload": { 1105 | "psr-4": { 1106 | "Symfony\\Component\\HttpFoundation\\": "" 1107 | }, 1108 | "exclude-from-classmap": [ 1109 | "/Tests/" 1110 | ] 1111 | }, 1112 | "notification-url": "https://packagist.org/downloads/", 1113 | "license": [ 1114 | "MIT" 1115 | ], 1116 | "authors": [ 1117 | { 1118 | "name": "Fabien Potencier", 1119 | "email": "fabien@symfony.com" 1120 | }, 1121 | { 1122 | "name": "Symfony Community", 1123 | "homepage": "https://symfony.com/contributors" 1124 | } 1125 | ], 1126 | "description": "Defines an object-oriented layer for the HTTP specification", 1127 | "homepage": "https://symfony.com", 1128 | "support": { 1129 | "source": "https://github.com/symfony/http-foundation/tree/v6.2.6" 1130 | }, 1131 | "funding": [ 1132 | { 1133 | "url": "https://symfony.com/sponsor", 1134 | "type": "custom" 1135 | }, 1136 | { 1137 | "url": "https://github.com/fabpot", 1138 | "type": "github" 1139 | }, 1140 | { 1141 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1142 | "type": "tidelift" 1143 | } 1144 | ], 1145 | "time": "2023-01-30T15:46:28+00:00" 1146 | }, 1147 | { 1148 | "name": "symfony/polyfill-mbstring", 1149 | "version": "v1.27.0", 1150 | "source": { 1151 | "type": "git", 1152 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1153 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" 1154 | }, 1155 | "dist": { 1156 | "type": "zip", 1157 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 1158 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 1159 | "shasum": "" 1160 | }, 1161 | "require": { 1162 | "php": ">=7.1" 1163 | }, 1164 | "provide": { 1165 | "ext-mbstring": "*" 1166 | }, 1167 | "suggest": { 1168 | "ext-mbstring": "For best performance" 1169 | }, 1170 | "type": "library", 1171 | "extra": { 1172 | "branch-alias": { 1173 | "dev-main": "1.27-dev" 1174 | }, 1175 | "thanks": { 1176 | "name": "symfony/polyfill", 1177 | "url": "https://github.com/symfony/polyfill" 1178 | } 1179 | }, 1180 | "autoload": { 1181 | "files": [ 1182 | "bootstrap.php" 1183 | ], 1184 | "psr-4": { 1185 | "Symfony\\Polyfill\\Mbstring\\": "" 1186 | } 1187 | }, 1188 | "notification-url": "https://packagist.org/downloads/", 1189 | "license": [ 1190 | "MIT" 1191 | ], 1192 | "authors": [ 1193 | { 1194 | "name": "Nicolas Grekas", 1195 | "email": "p@tchwork.com" 1196 | }, 1197 | { 1198 | "name": "Symfony Community", 1199 | "homepage": "https://symfony.com/contributors" 1200 | } 1201 | ], 1202 | "description": "Symfony polyfill for the Mbstring extension", 1203 | "homepage": "https://symfony.com", 1204 | "keywords": [ 1205 | "compatibility", 1206 | "mbstring", 1207 | "polyfill", 1208 | "portable", 1209 | "shim" 1210 | ], 1211 | "support": { 1212 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" 1213 | }, 1214 | "funding": [ 1215 | { 1216 | "url": "https://symfony.com/sponsor", 1217 | "type": "custom" 1218 | }, 1219 | { 1220 | "url": "https://github.com/fabpot", 1221 | "type": "github" 1222 | }, 1223 | { 1224 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1225 | "type": "tidelift" 1226 | } 1227 | ], 1228 | "time": "2022-11-03T14:55:06+00:00" 1229 | }, 1230 | { 1231 | "name": "symfony/routing", 1232 | "version": "v6.2.5", 1233 | "source": { 1234 | "type": "git", 1235 | "url": "https://github.com/symfony/routing.git", 1236 | "reference": "589bd742d5d03c192c8521911680fe88f61712fe" 1237 | }, 1238 | "dist": { 1239 | "type": "zip", 1240 | "url": "https://api.github.com/repos/symfony/routing/zipball/589bd742d5d03c192c8521911680fe88f61712fe", 1241 | "reference": "589bd742d5d03c192c8521911680fe88f61712fe", 1242 | "shasum": "" 1243 | }, 1244 | "require": { 1245 | "php": ">=8.1" 1246 | }, 1247 | "conflict": { 1248 | "doctrine/annotations": "<1.12", 1249 | "symfony/config": "<6.2", 1250 | "symfony/dependency-injection": "<5.4", 1251 | "symfony/yaml": "<5.4" 1252 | }, 1253 | "require-dev": { 1254 | "doctrine/annotations": "^1.12|^2", 1255 | "psr/log": "^1|^2|^3", 1256 | "symfony/config": "^6.2", 1257 | "symfony/dependency-injection": "^5.4|^6.0", 1258 | "symfony/expression-language": "^5.4|^6.0", 1259 | "symfony/http-foundation": "^5.4|^6.0", 1260 | "symfony/yaml": "^5.4|^6.0" 1261 | }, 1262 | "suggest": { 1263 | "symfony/config": "For using the all-in-one router or any loader", 1264 | "symfony/expression-language": "For using expression matching", 1265 | "symfony/http-foundation": "For using a Symfony Request object", 1266 | "symfony/yaml": "For using the YAML loader" 1267 | }, 1268 | "type": "library", 1269 | "autoload": { 1270 | "psr-4": { 1271 | "Symfony\\Component\\Routing\\": "" 1272 | }, 1273 | "exclude-from-classmap": [ 1274 | "/Tests/" 1275 | ] 1276 | }, 1277 | "notification-url": "https://packagist.org/downloads/", 1278 | "license": [ 1279 | "MIT" 1280 | ], 1281 | "authors": [ 1282 | { 1283 | "name": "Fabien Potencier", 1284 | "email": "fabien@symfony.com" 1285 | }, 1286 | { 1287 | "name": "Symfony Community", 1288 | "homepage": "https://symfony.com/contributors" 1289 | } 1290 | ], 1291 | "description": "Maps an HTTP request to a set of configuration variables", 1292 | "homepage": "https://symfony.com", 1293 | "keywords": [ 1294 | "router", 1295 | "routing", 1296 | "uri", 1297 | "url" 1298 | ], 1299 | "support": { 1300 | "source": "https://github.com/symfony/routing/tree/v6.2.5" 1301 | }, 1302 | "funding": [ 1303 | { 1304 | "url": "https://symfony.com/sponsor", 1305 | "type": "custom" 1306 | }, 1307 | { 1308 | "url": "https://github.com/fabpot", 1309 | "type": "github" 1310 | }, 1311 | { 1312 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1313 | "type": "tidelift" 1314 | } 1315 | ], 1316 | "time": "2023-01-01T08:38:09+00:00" 1317 | } 1318 | ], 1319 | "packages-dev": [], 1320 | "aliases": [], 1321 | "minimum-stability": "stable", 1322 | "stability-flags": [], 1323 | "prefer-stable": false, 1324 | "prefer-lowest": false, 1325 | "platform": [], 1326 | "platform-dev": [], 1327 | "plugin-api-version": "2.3.0" 1328 | } 1329 | -------------------------------------------------------------------------------- /src/chat-server.php: -------------------------------------------------------------------------------- 1 | enableKeepAlive($loop, 10); 16 | $app = new HttpServer($wsServer); 17 | $secureSocketServer = new SocketServer(Config::get('SERVER_URI'), [ 18 | 'tls' => [ 19 | 'local_cert' => Config::get('LOCAL_CERT_PATH'), 20 | 'local_pk' => Config::get('LOCAL_PK_PATH'), 21 | 'allow_self_signed' => false, 22 | 'verify_peer' => true 23 | ] 24 | ], $loop); 25 | 26 | $wss = new IoServer($app, $secureSocketServer, $loop); 27 | $wss->run(); 28 | -------------------------------------------------------------------------------- /src/config/config.DEV.php: -------------------------------------------------------------------------------- 1 | 'mysql', 5 | 'DB_HOST' => 'localhost', 6 | 'DB_NAME' => 'local_db_name', 7 | 'DB_USER' => 'local_db_user', 8 | 'DB_PASS' => 'local_db_pwd', 9 | 'DB_CHARSET' => 'utf8', 10 | 'SERVER_URI' => '0.0.0.0:8080', 11 | 12 | // full list of timezones can be found here https://www.php.net/manual/en/timezones.php 13 | // we are using the default here 14 | 'TIMEZONE' => date_default_timezone_get(), 15 | 16 | // the necryption key used by your website when creating the encrypted auth details 17 | 'CHAT_SOCKET_SERVER_CIPHER_KEY' => "sdkj478ksfdj83erhrui", 18 | 19 | // this is to be used with the OriginCheck which is not used 20 | 'ALLOWED_DOMAINS' => [], 21 | 22 | 'LOCAL_CERT_PATH' => '', // Path to fullchain.pem 23 | 'LOCAL_PK_PATH' => '' // Path to privkey.pem 24 | ]; 25 | -------------------------------------------------------------------------------- /src/config/config.PROD.php: -------------------------------------------------------------------------------- 1 | 'mysql', 5 | 'DB_HOST' => 'production_db_host_url', 6 | 'DB_NAME' => 'prod_db_name', 7 | 'DB_USER' => 'prod_db_user', 8 | 'DB_PASS' => 'prod_db_pwd', 9 | 'DB_CHARSET' => 'utf8', 10 | 'SERVER_URI' => 'tls://0.0.0.0:443', 11 | 12 | // full list of timezones can be found here https://www.php.net/manual/en/timezones.php 13 | // we are using the default here 14 | 'TIMEZONE' => date_default_timezone_get(), 15 | 16 | // the necryption key used by your website when creating the encrypted auth details 17 | 'CHAT_SOCKET_SERVER_CIPHER_KEY' => "1234", 18 | 19 | // this is to be used with the OriginCheck which is not used 20 | 'ALLOWED_DOMAINS' => [], 21 | 22 | 'LOCAL_CERT_PATH' => '', // Path to fullchain.pem 23 | 'LOCAL_PK_PATH' => '' // Path to privkey.pem 24 | ]; 25 | -------------------------------------------------------------------------------- /src/core/AESCipher.php: -------------------------------------------------------------------------------- 1 | key = $key; 13 | $this->cipher = $cipher; 14 | } 15 | 16 | public function encrypt(string $plaintext): string 17 | { 18 | $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cipher)); 19 | $ciphertext = openssl_encrypt($plaintext, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv); 20 | 21 | return base64_encode($iv . $ciphertext); 22 | } 23 | 24 | public function decrypt(string $encryptedData): string 25 | { 26 | $data = base64_decode($encryptedData); 27 | $ivlen = openssl_cipher_iv_length($this->cipher); 28 | $iv = substr($data, 0, $ivlen); 29 | $ciphertext = substr($data, $ivlen); 30 | 31 | return openssl_decrypt($ciphertext, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/core/App.php: -------------------------------------------------------------------------------- 1 | rooms = new \SplObjectStorage; 18 | $this->db = new Database(Config::get('DB_HOST'), Config::get('DB_NAME'), Config::get('DB_USER'), Config::get('DB_PASS')); 19 | } 20 | 21 | public function onOpen(ConnectionInterface $conn) 22 | { 23 | 24 | $data = Auth::connection($conn); 25 | if (!$data) { 26 | return; 27 | } 28 | 29 | $user_id = $data['user_id']; 30 | $chat_id = $data['chat_id']; 31 | 32 | Console::out("New connection: {$conn->resourceId}", Console::COLOR_GREEN); 33 | 34 | // if chat room exists then connect the user to it, if not then create the room 35 | $found_room = $this->roomExists($chat_id); 36 | if ($found_room !== false) { 37 | $found_room->addClient(new Client($conn, $user_id)); 38 | Console::out("Connection {$conn->resourceId} with user_id {$user_id} connected to EXISTING room with chat_id {$chat_id}."); 39 | } else { // else create the room 40 | $this->createRoom($chat_id, new Client($conn, $user_id)); 41 | Console::out("Connection {$conn->resourceId} with user_id {$user_id} connected to NEW room with chat_id {$chat_id}."); 42 | } 43 | 44 | return; 45 | } 46 | 47 | public function onMessage(ConnectionInterface $from_conn, $msg) 48 | { 49 | if (empty($msg)) { 50 | return; 51 | } 52 | 53 | // check if user is authenticated and send connection 54 | foreach ($this->rooms as $room) { 55 | if ($room->hasClientWithConnection($from_conn)) { 56 | 57 | $room->sendMessage($from_conn, $msg); 58 | return; 59 | } 60 | } 61 | 62 | return; 63 | } 64 | 65 | public function onClose(ConnectionInterface $conn) 66 | { 67 | 68 | // remove client from chat room 69 | foreach ($this->rooms as $room) { 70 | $room->removeClientWithConnection($conn); 71 | // check if chat room empty, if so then remove the chat room 72 | if ($room->getClientsCount() === 0) { 73 | $this->rooms->detach($room); 74 | } 75 | } 76 | 77 | $conn->close(); 78 | 79 | Console::out("Connection {$conn->resourceId} was disconnected", Console::COLOR_RED); 80 | } 81 | 82 | public function onError(ConnectionInterface $conn, \Exception $e) 83 | { 84 | $conn->close(); 85 | } 86 | 87 | public function createRoom(int $chat_id, Client $first_client) 88 | { 89 | $this->rooms->attach(new ChatRoom($chat_id, $first_client)); 90 | } 91 | 92 | public function roomExists(int $chat_id): ChatRoom|false 93 | { 94 | foreach ($this->rooms as $room) { 95 | if ($room->getId() === $chat_id) { 96 | return $room; 97 | } 98 | } 99 | 100 | return false; 101 | } 102 | 103 | public function deleteRoom(int $chat_id): bool 104 | { 105 | foreach ($this->rooms as $room) { 106 | if ($room->getId() === $chat_id) { 107 | $this->rooms->detach($room); 108 | return true; 109 | } 110 | } 111 | 112 | return false; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/core/Auth.php: -------------------------------------------------------------------------------- 1 | httpRequest->getHeader('Cookie'); 22 | // if (empty($cookiesRaw)) { 23 | // $cookiesArr = \GuzzleHttp\Psr7\parse_header($cookiesRaw)[0]; 24 | // } 25 | 26 | // get the query parameters 27 | $query_string = $conn->httpRequest->getUri()->getQuery(); 28 | 29 | if (empty($query_string)) { 30 | $conn->close(); 31 | Console::out("No query given.", Console::COLOR_RED); 32 | return false; 33 | } 34 | 35 | parse_str($query_string, $query_array); 36 | 37 | // validate authentication details 38 | if (empty($query_array['auth_details'])) { 39 | $conn->close(); 40 | Console::out("No auth details given.", Console::COLOR_RED); 41 | return false; 42 | } 43 | 44 | $cipher = new AESCipher(Config::get('CHAT_SOCKET_SERVER_CIPHER_KEY')); 45 | 46 | $auth_details_decrypted = $cipher->decrypt(base64_decode($query_array['auth_details'])); 47 | 48 | // json to object 49 | $auth_details = json_decode($auth_details_decrypted); 50 | 51 | if (!$auth_details) { 52 | $conn->close(); 53 | Console::out("Auth details is invalid json.", Console::COLOR_RED); 54 | return false; 55 | } 56 | 57 | // in the auth details we expect user_id, chat_id and auth_token 58 | if (empty($auth_details->user_id) || empty($auth_details->chat_id) || empty($auth_details->auth_token)) { 59 | $conn->close(); 60 | Console::out("Not all details were given.", Console::COLOR_RED); 61 | return false; 62 | } 63 | 64 | $user_id = $auth_details->user_id; 65 | $chat_id = $auth_details->chat_id; 66 | $auth_token = $auth_details->auth_token; 67 | 68 | // auth token must be 32 chars length 69 | if (strlen($auth_token) !== 32) { 70 | $conn->close(); 71 | Console::out("Auth token invalid.", Console::COLOR_RED); 72 | return false; 73 | } 74 | 75 | // TODO: self authenticate without the need of the website 76 | // authenticate with token 77 | if (!Database::row("SELECT id FROM chat_auth_token WHERE token=? AND used=0", [$auth_token])) { 78 | $conn->close(); 79 | Console::out("Token is used.", Console::COLOR_RED); 80 | return false; 81 | } 82 | 83 | // set token to used 84 | Database::update('chat_auth_token', ['used' => 1], ['token' => $auth_token]); 85 | 86 | return ['user_id' => $user_id, 'chat_id' => $chat_id]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/core/Config.php: -------------------------------------------------------------------------------- 1 | false, 38 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 39 | ]; 40 | 41 | try { 42 | self::$pdo = new PDO($dsn, self::$username, self::$password, $options); 43 | 44 | // keep connection for 1 hour from last sql 45 | self::raw("SET SESSION wait_timeout=3600;"); 46 | 47 | Console::out("Application successfully connected to the database", Console::COLOR_BLUE); 48 | } catch (PDOException $error) { 49 | Console::out("Database connection failed with error: {$error->getMessage()}", Console::COLOR_RED); 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | /** 57 | * Ping the database server to check if connection is active 58 | * 59 | * @return bool successful ping 60 | */ 61 | public static function ping(): bool 62 | { 63 | try { 64 | self::$pdo->query("SELECT 1"); 65 | } catch (PDOException $e) { 66 | self::connect(); 67 | } 68 | 69 | return true; 70 | } 71 | 72 | /** 73 | * Checks if the connection with the database is active, if not then it reconencts 74 | * 75 | * @return bool successful reconnection 76 | */ 77 | public static function checkConenction(): bool 78 | { 79 | 80 | // if connection not active, reconnect 81 | if (!self::ping()) { 82 | return (self::connect()) ? true : false; 83 | } 84 | 85 | return true; 86 | } 87 | 88 | /** 89 | * Run raw sql query 90 | * 91 | * @param string $sql sql query 92 | * @return void 93 | */ 94 | public static function raw($sql) 95 | { 96 | 97 | if (!self::checkConenction()) { 98 | return false; 99 | } 100 | 101 | self::$pdo->query($sql); 102 | } 103 | 104 | /** 105 | * Run sql query 106 | * 107 | * @param string $sql sql query 108 | * @param array $args params 109 | * @return object returns a PDO object 110 | */ 111 | public static function run($sql, $args = []) 112 | { 113 | if (!self::checkConenction()) { 114 | return false; 115 | } 116 | 117 | if (empty($args)) { 118 | return self::$pdo->query($sql); 119 | } 120 | 121 | $stmt = self::$pdo->prepare($sql); 122 | $stmt->execute($args); 123 | 124 | return $stmt; 125 | } 126 | 127 | /** 128 | * Get primary key of last inserted record 129 | */ 130 | public static function lastInsertId() 131 | { 132 | if (!self::checkConenction()) { 133 | return false; 134 | } 135 | 136 | return self::$pdo->lastInsertId(); 137 | } 138 | 139 | /** 140 | * Insert record 141 | * 142 | * @param string $table table name 143 | * @param array $data array of columns and values 144 | */ 145 | public static function insert($table, $data) 146 | { 147 | 148 | //add columns into comma seperated string 149 | $columns = implode(',', array_keys($data)); 150 | 151 | //get values 152 | $values = array_values($data); 153 | 154 | $placeholders = array_map(function ($val) { 155 | return '?'; 156 | }, array_keys($data)); 157 | 158 | //convert array into comma seperated string 159 | $placeholders = implode(',', array_values($placeholders)); 160 | 161 | self::run("INSERT INTO $table ($columns) VALUES ($placeholders)", $values); 162 | 163 | return self::lastInsertId(); 164 | } 165 | 166 | /** 167 | * Get arrray of records 168 | * 169 | * @param string $sql sql query 170 | * @param array $args params 171 | * @param object $fetchMode set return mode ie object or array 172 | * @return object returns single record 173 | */ 174 | public static function row($sql, $args = [], $fetchMode = PDO::FETCH_OBJ) 175 | { 176 | return self::run($sql, $args)->fetch($fetchMode); 177 | } 178 | 179 | /** 180 | * Get arrrays of records 181 | * 182 | * @param string $sql sql query 183 | * @param array $args params 184 | * @param object $fetchMode set return mode ie object or array 185 | * @return object returns multiple records 186 | */ 187 | public static function rows($sql, $args = [], $fetchMode = PDO::FETCH_OBJ) 188 | { 189 | return self::run($sql, $args)->fetchAll($fetchMode); 190 | } 191 | 192 | /** 193 | * Update record 194 | * 195 | * @param string $table table name 196 | * @param array $data array of columns and values 197 | * @param array $where array of columns and values 198 | */ 199 | public static function update($table, $data, $where) 200 | { 201 | //merge data and where together 202 | $collection = array_merge($data, $where); 203 | 204 | //collect the values from collection 205 | $values = array_values($collection); 206 | 207 | //setup fields 208 | $fieldDetails = null; 209 | foreach ($data as $key => $value) { 210 | $fieldDetails .= "$key = ?,"; 211 | } 212 | $fieldDetails = rtrim($fieldDetails, ','); 213 | 214 | //setup where 215 | $whereDetails = null; 216 | $i = 0; 217 | foreach ($where as $key => $value) { 218 | $whereDetails .= $i == 0 ? "$key = ?" : " AND $key = ?"; 219 | $i++; 220 | } 221 | 222 | $stmt = self::run("UPDATE $table SET $fieldDetails WHERE $whereDetails", $values); 223 | 224 | return $stmt->rowCount(); 225 | } 226 | 227 | /** 228 | * Get number of records 229 | * 230 | * @param string $sql sql query 231 | * @param array $args params 232 | * @param object $fetchMode set return mode ie object or array 233 | * @return integer returns number of records 234 | */ 235 | public static function count($sql, $args = []) 236 | { 237 | return self::run($sql, $args)->rowCount(); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/core/Environment.php: -------------------------------------------------------------------------------- 1 | clients = []; 15 | $this->clients[$first_client->getUserId()] = $first_client; 16 | } 17 | 18 | public function getId(): int 19 | { 20 | return $this->chat_room_id; 21 | } 22 | 23 | /** 24 | * Sends a message to all clients in the chat room except the sender. 25 | * 26 | * @param ConnectionInterface $from_conn The connection of the sender. 27 | * @param string $text The message text. 28 | * @return bool True on success, false on failure. 29 | */ 30 | public function sendMessage(ConnectionInterface $from_conn, string $text): bool 31 | { 32 | $user_id = $this->getClientIdFromConnection($from_conn); 33 | 34 | if ($user_id === false) { 35 | return false; 36 | } 37 | 38 | $text = trim($text); 39 | 40 | Database::insert('chat_line', ['chat_id' => $this->chat_room_id, 'sender_user_id' => $user_id, 'message' => $text]); 41 | 42 | foreach ($this->clients as $client_id => $client) { 43 | if ($client->getConnection() !== $from_conn) { 44 | $client->sendMessage(json_encode([ 45 | 'text' => htmlspecialchars($text), 46 | 'sent_at' => date('Y-m-d H:i:s') 47 | ])); 48 | } 49 | } 50 | 51 | return true; 52 | } 53 | 54 | public function addClient(Client $client_to_add): bool 55 | { 56 | $this->clients[$client_to_add->getUserId()] = $client_to_add; 57 | return true; 58 | } 59 | 60 | public function getClientIdFromConnection(ConnectionInterface $conn): int|false 61 | { 62 | foreach ($this->clients as $client_id => $client) { 63 | if ($conn === $client->getConnection()) { 64 | return $client_id; 65 | } 66 | } 67 | 68 | return false; 69 | } 70 | 71 | public function getClientIDs(): array 72 | { 73 | return array_keys($this->clients); 74 | } 75 | 76 | public function hasClient(Client $client): bool 77 | { 78 | return isset($this->clients[$client->getUserId()]); 79 | } 80 | 81 | public function hasClientWithConnection(ConnectionInterface $conn): bool 82 | { 83 | return $this->getClientIdFromConnection($conn) !== false; 84 | } 85 | 86 | public function removeClient(Client $client): bool 87 | { 88 | unset($this->clients[$client->getUserId()]); 89 | return true; 90 | } 91 | 92 | public function removeClientWithConnection(ConnectionInterface $conn): bool 93 | { 94 | $user_id = $this->getClientIdFromConnection($conn); 95 | 96 | if ($user_id !== false) { 97 | unset($this->clients[$user_id]); 98 | return true; 99 | } 100 | 101 | return false; 102 | } 103 | 104 | public function getClientsCount(): int 105 | { 106 | return count($this->clients); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/models/Client.php: -------------------------------------------------------------------------------- 1 | conn; 16 | } 17 | 18 | public function getUserId(): int 19 | { 20 | return $this->user_id; 21 | } 22 | 23 | public function sendMessage(string $text) 24 | { 25 | $this->conn->send($text); 26 | } 27 | } 28 | --------------------------------------------------------------------------------