├── .gitignore ├── .gitmodules ├── README.md ├── README_RUS.md ├── composer.json ├── examples └── events │ ├── controllers │ └── NsEventExampleController.php │ └── views │ ├── eventListener.php │ ├── index.php │ ├── sendEvent.php │ └── sendRoomEvent.php ├── lib ├── js │ ├── client │ │ └── client.template.js │ └── server │ │ ├── components │ │ ├── autoload │ │ │ ├── channel.manager.js │ │ │ └── public.data.js │ │ ├── component.manager.js │ │ ├── db.js │ │ ├── db │ │ │ ├── dummy │ │ │ │ └── connector.js │ │ │ └── mysql │ │ │ │ └── connector.js │ │ ├── event.manager.js │ │ └── socket.pull.js │ │ ├── events │ │ ├── client │ │ │ ├── channel.join.js │ │ │ ├── channel.leave.js │ │ │ ├── event.emit.js │ │ │ ├── public.data.js │ │ │ ├── room.join.js │ │ │ └── room.leave.js │ │ └── server │ │ │ ├── auth.js │ │ │ ├── channel_event.js │ │ │ ├── event.js │ │ │ ├── invoke.js │ │ │ ├── jquery.js │ │ │ ├── logout.js │ │ │ ├── multiple.frame.js │ │ │ ├── public.data.js │ │ │ └── userEvent.js │ │ ├── package.json │ │ ├── server.config.js.php │ │ └── server.js └── php │ ├── Autoload.php │ ├── NodeSocket.php │ ├── NodeSocketCommand.php │ ├── assets │ └── NodeSocketAssets.php │ ├── behaviors │ ├── ABehavior.php │ ├── ArBehavior.php │ ├── ArChannel.php │ ├── ArChannelTrigger.php │ └── ArSubscriber.php │ ├── components │ ├── AComponent.php │ ├── ArEvent.php │ ├── Db.php │ ├── db │ │ ├── BaseDriver.php │ │ ├── DriverInterface.php │ │ ├── dummy │ │ │ └── DummyDriver.php │ │ └── mysql │ │ │ ├── MysqlDriver.php │ │ │ ├── m131126_114536_node_socket_migration.php │ │ │ └── models │ │ │ ├── NsChannel.php │ │ │ ├── NsSubscriber.php │ │ │ └── NsSubscriberChannel.php │ └── frames │ │ └── JQuerySelector.php │ ├── console │ ├── ConsoleInterface.php │ ├── UnixConsole.php │ └── WinConsole.php │ ├── frames │ ├── AFrame.php │ ├── Authentication.php │ ├── ChannelEvent.php │ ├── Event.php │ ├── FrameFactory.php │ ├── IFrameFactory.php │ ├── Invoke.php │ ├── JQuery.php │ ├── LogoutFrame.php │ ├── Multiple.php │ ├── PublicData.php │ ├── RuntimeServerConfiguration.php │ └── UserEvent.php │ └── models │ ├── AModel.php │ ├── Channel.php │ ├── Subscriber.php │ └── SubscriberChannel.php └── tests └── php ├── components └── db │ ├── BaseDriverTest.php │ └── mysql │ └── MysqlDriverTest.php ├── frames ├── AFrameTest.php ├── EventTest.php └── InvokeTest.php └── models ├── AModelTest.php ├── ChannelTest.php └── SubscriberTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | client.js 3 | lib/js/server/server.config.js 4 | tests/php/bootstrap.php 5 | /nbproject 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/vendor/elephant.io"] 2 | path = lib/vendor/elephant.io 3 | url = https://github.com/oncesk/elephant.io.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii Node Socket 2 | ================= 3 | 4 | [![Join the chat at https://gitter.im/oncesk/yii-node-socket](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/oncesk/yii-node-socket?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Connect php, javascript, nodejs in one Yii application. 7 | 8 | ####Yii1 9 | 10 | Hi, if you need work with yii1 you can do it from (https://github.com/oncesk/yii-node-socket/tree/2.0.0) 11 | 12 | ####What you can do: 13 | 14 | - send event(s) to all clients or in concrete room or in concrete channel 15 | - now you can send events to single (concrete user) by user id! 16 | - call some function or object method in window context 17 | - you can change DOM model with jquery from php 18 | - ability to set up data and get it in your javascript application 19 | - send events from javascript for all clients or clients in concrete room or channel 20 | 21 | ##Changes 22 | - Updated for Yii 2.0 23 | - Added Namespacing Updated for Yii 2.0 24 | - Commposer settings updated to work with yii extension updates 25 | 26 | ##Requirements 27 | 28 | - linux/unix/windows 29 | - git 30 | - vps or dedicated server (for nodejs process) 31 | - curl has to be installed or enabled 32 | 33 | #Installation 34 | 35 | Install nodejs, if not installed see http://nodejs.org/
36 | Install extension 37 | 38 | * Composer 39 | 40 | ```javascript 41 | { 42 | "require" : { 43 | "oncesk/yii-node-socket" : "2.0.4" 44 | } 45 | } 46 | ``` 47 | 48 | * Using git clone 49 | 50 | ```bash 51 | $> git clone https://github.com/oncesk/yii-node-socket.git 52 | ``` 53 | 54 | Now go to the folder where you install extension ***application.ext.yii-node-socket*** and execute
55 | ```bash 56 | $> git submodule init 57 | $> git submodule update 58 | ``` 59 | 60 | Yii configuration
61 | * Configure console command in (***console/config/main.php***). You can use config below: 62 | 63 | ```php 64 | 'controllerMap' => [ 65 | 'node-socket' => '\YiiNodeSocket\NodeSocketCommand', 66 | ], 67 | ``` 68 | 69 | * Register Yii component, need to add into **frontend/config/main.php in your frontend application**: 70 | 71 | ```php 72 | 'nodeSocket' => [ 73 | 'class' => '\YiiNodeSocket\NodeSocket', 74 | 'host' => 'localhost', 75 | 'allowedServerAddresses' => [ 76 | "localhost", 77 | "127.0.0.1" 78 | ], 79 | 'origin' => '*:*', 80 | 'sessionVarName' => 'PHPSESSID', 81 | 'port' => 3001, 82 | 'socketLogFile' => '/var/log/node-socket.log', 83 | ], 84 | ``` 85 | > Notice: ***host*** should be a domain name like in you virtual host configuration or server ip address if you request page using ip address 86 | 87 | * Configure aliases in the common config in (***common/config/main.php***). 88 | * The first is for Yii to find the PHP Namespace and the second is to find the JS assets. 89 | * You can use config below: 90 | 91 | ```php 92 | 93 | 'aliases' => [ 94 | '@YiiNodeSocket' => '@vendor/oncesk/yii-node-socket/lib/php', 95 | '@nodeWeb' => '@vendor/oncesk/yii-node-socket/lib/js' 96 | ], 97 | ``` 98 | 99 | 100 | > Notice: if you will be use ***behaviors*** or node-socket models, you need to add nodeSocket component in ***preload*** components list 101 | 102 | ```php 103 | 104 | 'bootstrap' => ['log', 'nodeSocket'], 105 | 106 | ``` 107 | 108 | Install ***nodejs*** components in ***application.ext.yii-node-socket.lib.js.server***: 109 | ```bash 110 | $> npm install 111 | ``` 112 | If you get errors issuing this command try the following: 113 | ```bash 114 | $> npm install --no-bin-links 115 | ``` 116 | 117 | Congratulation, installation completed! 118 | 119 | > Notice: if the name of the component will not be **nodeSocket**, your need to use special key in console command --componentName=component_name 120 | 121 | ###Console command actions 122 | 123 | Use (**./yiic node-socket**) 124 | 125 | ```bash 126 | $> ./yiic help node-socket # show help 127 | $> ./yiic node-socket/start # start server 128 | $> ./yiic node-socket/stop # stop server 129 | $> ./yiic node-socket/restart # restart server 130 | $> ./yiic node-socket/getPid # show pid of nodejs process 131 | ``` 132 | 133 | ##Definitions 134 | 135 | - Frame - data package for nodejs server wrapped into Class. Per one request to nodejs server you can send only 1 frame. For send several frames at a time use Multiple frame. 136 | - room - one or more clients in concrete namespace: every client can create room, other clients can join into concrete room, any client in room can send event in this room. 137 | 138 | ##Javascript 139 | 140 | Before use in javascript, register client stripts like here 141 | Depricated in Yii 2.0 - The Asset Manager registers the files on demand. 142 | ```php 143 | public function actionIndex() { 144 | // register node socket scripts 145 | // No longer needed - Yii::app()->nodeSocket->registerClientScripts(); 146 | } 147 | ``` 148 | 149 | ###Events 150 | 151 | ###Work in javascript 152 | 153 | Use `YiiNodeSocket` class 154 | 155 | ####Start work 156 | 157 | ```javascript 158 | 159 | // create object 160 | var socket = new YiiNodeSocket(); 161 | 162 | // enable debug mode 163 | socket.debug(true); 164 | 165 | socket.onConnect(function () { 166 | // fire when connection established 167 | }); 168 | 169 | socket.onDisconnect(function () { 170 | // fire when connection close or lost 171 | }); 172 | 173 | socket.onConnecting(function () { 174 | // fire when the socket is attempting to connect with the server 175 | }); 176 | 177 | socket.onReconnect(function () { 178 | // fire when successfully reconnected to the server 179 | }); 180 | ``` 181 | 182 | ####Catch Events 183 | 184 | Now events can be created only on PHP side. All data transmitted in json format. 185 | Into callback function data was pasted as javascript native object (or string, integer, depends of your PHP Frame config) 186 | 187 | ```javascript 188 | // add event listener 189 | socket.on('updateBoard', function (data) { 190 | // do any action 191 | }); 192 | ``` 193 | 194 | ####Rooms 195 | 196 | ```javascript 197 | socket.onConnect(function () { 198 | socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { 199 | // success - boolean, numberOfRoomSubscribers - number of room members 200 | // if error occurred then success = false, and numberOfRoomSubscribers - contains error message 201 | if (success) { 202 | console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); 203 | // do something 204 | 205 | // bind events 206 | this.on('join', function (newMembersCount) { 207 | // fire on client join 208 | }); 209 | 210 | this.on('data', function (data) { 211 | // fire when server send frame into this room with 'data' event 212 | }); 213 | } else { 214 | // numberOfRoomSubscribers - error message 215 | alert(numberOfRoomSubscribers); 216 | } 217 | }); 218 | }); 219 | 220 | ``` 221 | 222 | ####Channels 223 | 224 | Channel is very similar to the room, but you can controll access to channel for clients 225 | 226 | ```javascript 227 | 228 | // join to channel, join needed when you try subscribe to channel from javascript, if you subscribed to channel in php you can bind events without join 229 | socket.onConnect(function () { 230 | var testChannel = socket.channel('test').join(function (success) { 231 | // success - boolean 232 | if (success) { 233 | // fore getting channel attributes 234 | console.log(this.getAttributes()); 235 | 236 | // bind event listeners 237 | this.on('some_event', function (data) { 238 | // fire when server send frame into this room with 'data' event 239 | }); 240 | } else { 241 | console.log(this.getError()); 242 | } 243 | }); 244 | 245 | // you can bind events handlers for some events without join 246 | // in this case you should be subscribed to `test` channel 247 | socket.channel('test').on('some_event', function (data) { 248 | }); 249 | 250 | }); 251 | ``` 252 | 253 | ####Emit events 254 | 255 | You can emit event to: 256 | - all clients (including the event sender) 257 | - all clients (excluding the event sender - broadcasting. Only javascript currently supports broadcasting. PHP broadcasting coming soon) 258 | - clients in concrete room 259 | 260 | Global events: 261 | 262 | ```javascript 263 | 264 | socket.emit('global.event', { 265 | message : { 266 | id : 12, 267 | title : 'This is a test message to all including sender' 268 | } 269 | }); 270 | 271 | socket.broadcast.emit('global.event', { 272 | message : { 273 | id : 12, 274 | title : 'This is a test message to all excluding sender' 275 | } 276 | }); 277 | 278 | socket.on('global.event', function (data) { 279 | console.log(data.message.title); // you will see in console `This is a test message` 280 | }); 281 | 282 | ``` 283 | 284 | Room event: 285 | 286 | ```javascript 287 | socket.onConnect(function () { 288 | var testRoom = socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { 289 | // success - boolean, numberOfRoomSubscribers - number of room members 290 | // if error occurred then success = false, and numberOfRoomSubscribers - contains error message 291 | if (success) { 292 | console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); 293 | // do something 294 | 295 | // bind events 296 | this.on('message', function (message) { 297 | console.log(message); 298 | }); 299 | 300 | this.on('ping', function () { 301 | console.log('Ping!'); 302 | }); 303 | 304 | this.emit('ping'); // emit ping event 305 | } else { 306 | // numberOfRoomSubscribers - error message 307 | alert(numberOfRoomSubscribers); 308 | } 309 | }); 310 | 311 | // emit message event 312 | testRoom.emit('message', { 313 | message : { 314 | id : 12, 315 | title : 'This is a test message' 316 | } 317 | }); 318 | }); 319 | 320 | ``` 321 | 322 | ####Shared Public Data 323 | 324 | You can set shared data only from PHP using PublicData Frame (see below into PHP section). 325 | To access data you can use `getPublicData(string key, callback fn)` method 326 | 327 | ```javascript 328 | socket.getPublicData('error.strings', function (strings) { 329 | // you need to check if strings exists, because strings can be not setted or expired, 330 | if (strings) { 331 | // do something 332 | } 333 | }); 334 | ``` 335 | 336 | ##PHP 337 | 338 | ####Behaviors 339 | 340 | - YiiNodeSocket\Behaviors\ArChannel - can be used for create new channel. Example: You can attach this behavior to User, in result any user will have own channel, and other user can subscribe to events of concrete user. 341 | - YiiNodeSocket\Behaviors\ArSubscriber - should be attached to object which can subscribe to some channel. Example: model User at a time can be channel and subscriber. 342 | 343 | ```php 344 | 345 | /** 346 | * 347 | * @method \YiiNodeSocket\Models\Channel getChannel() 348 | * @method \YiiNodeSocket\Frames\Event|null createEvent($name) 349 | */ 350 | class User extends CActiveRecord { 351 | 352 | ... 353 | 354 | public function behaviors() { 355 | return array( 356 | // attach channel behavior 357 | 'channel' => array( 358 | 'class' => '\YiiNodeSocket\Behaviors\ArChannel', 359 | 'updateOnSave' => true 360 | ), 361 | // attach subscriber behavior 362 | 'subscriber' => array( 363 | 'class' => '\YiiNodeSocket\Behaviors\ArSubscriber' 364 | ) 365 | ); 366 | } 367 | 368 | ... 369 | 370 | } 371 | 372 | // Example of subscribe 373 | 374 | $user1 = new User(); 375 | $user1->setAttributes($attributes); 376 | if ($user1->save()) { 377 | // imagine that $user1->id == 122 378 | // user channel was created and sent to nodejs server 379 | // subscriber was created and sent to nodejs server 380 | 381 | // create second user 382 | $user2 = User::model()->findByPk(121); 383 | 384 | // now we can subscribe one user to second user 385 | $user1->subscribe($user2); 386 | // and $user2 can catch events from $user1 channel like in twitter 387 | 388 | 389 | } 390 | 391 | 392 | // Example of emit event in concrete channel 393 | 394 | $user = User::model()->findByPk(122); 395 | if ($user) { 396 | 397 | // First method 398 | $event = $user->createEvent('test_event'); 399 | if ($event) { 400 | // set event data 401 | $event->setData(array( 402 | 'black', 'red', 'white' 403 | )); 404 | // send event to user channel 405 | $event->send(); 406 | } 407 | 408 | // Second method with getting channel 409 | $channel = $user->getChannel(); 410 | if ($channel) { 411 | $event = $channel->createEvent('test_event'); 412 | // set event data 413 | $event->setData(array( 414 | 'black', 'red', 'white' 415 | )); 416 | // send event to user channel 417 | $event->send(); 418 | } 419 | } 420 | 421 | 422 | // Example of unsubscribe 423 | 424 | $user1 = User::model()->findByPk(122); 425 | $user2 = User::model()->findByPk(121); 426 | 427 | $user1->unSubscribe($user2); // now $user2 can not catch events in channel of $user1 428 | 429 | ``` 430 | 431 | ####Client authorization 432 | 433 | For authorizing client you need send special Authentication frame, you can do it in your ***user*** component in afterLogin event 434 | 435 | ```php 436 | 437 | protected function afterLogin($fromCookie) { 438 | parent::afterLogin($fromCookie); 439 | 440 | $frame = Yii::app()->nodeSocket->getFrameFactory()->createAuthenticationFrame(); 441 | $frame->setUserId($this->getId()); 442 | $frame->send(); 443 | } 444 | 445 | 446 | ``` 447 | 448 | After that, this user can receive events only for him. 449 | See example below, send event only for single (concrete) $user 450 | 451 | ```php 452 | 453 | // $user - the user model, which can receive this event 454 | 455 | $event = Yii::app()->nodeSocket->getFrameFactory()->createUserEventFrame(); 456 | $event->setUserId($user->id); 457 | $event->setEventName('message'); 458 | $event['text'] = 'Hello, how are you?'; 459 | $event->send(); 460 | 461 | ``` 462 | 463 | and your javascript 464 | 465 | ```javascript 466 | 467 | var socket = new YiiNodeSocket(); 468 | socket.on('message', function (message) { 469 | console.log(message.text); 470 | // render message 471 | }); 472 | 473 | ``` 474 | 475 | ####Client scripts registration 476 | 477 | ```php 478 | public function actionIndex() { 479 | ... 480 | 481 | Yii::app()->nodeSocket->registerClientScripts(); 482 | 483 | ... 484 | } 485 | ``` 486 | 487 | ####Event frame 488 | 489 | ```php 490 | 491 | ... 492 | 493 | // create event frame 494 | $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); 495 | 496 | // set event name 497 | $frame->setEventName('updateBoard'); 498 | 499 | // set data using ArrayAccess interface 500 | $frame['boardId'] = 25; 501 | $frame['boardData'] = $html; 502 | 503 | // or you can use setData(array $data) method 504 | // setData overwrite data setted before 505 | 506 | $frame->send(); 507 | 508 | ... 509 | 510 | ``` 511 | 512 | ####Set up shared data 513 | 514 | You can set expiration using ***setLifeTime(integer $lifetime)*** method of class PublicData 515 | 516 | ```php 517 | 518 | ... 519 | 520 | // create frame 521 | $frame = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); 522 | 523 | // set key in storage 524 | $frame->setKey('error.strings'); 525 | 526 | // set data 527 | $frame->setData($errorStrings); 528 | 529 | // you can set data via ArrayAccess interface 530 | // $frame['empty_name'] = 'Please enter name'; 531 | 532 | // set data lifetime 533 | $frame->setLifeTime(3600*2); // after two hours data will be deleted from storage 534 | 535 | // send 536 | $frame->send(); 537 | 538 | ... 539 | 540 | ``` 541 | 542 | ####Room events 543 | 544 | ```php 545 | 546 | ... 547 | 548 | // create frame 549 | $frame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); 550 | 551 | // set event name 552 | $frame->setEventName('updateBoard'); 553 | 554 | // set room name 555 | $frame->setRoom('testRoom'); 556 | 557 | // set data 558 | $frame['key'] = $value; 559 | 560 | // send 561 | $frame->send(); 562 | 563 | ... 564 | 565 | ``` 566 | 567 | Only member of testRoom can catch this event 568 | 569 | ####Invoke client function or method 570 | 571 | In your PHP application you can invoke javascript function or method of object in window context. 572 | 573 | ```php 574 | 575 | $invokeFrame = Yii::app()->nodeSocket->getFrameFactory()->createInvokeFrame(); 576 | $invokeFrame->invokeFunction('alert', array('Hello world')); 577 | $invokeFrame->send(); // alert will be showed on all clients 578 | 579 | ``` 580 | 581 | Extends from Event frame => you can send it into specific room 582 | 583 | ####DOM manipulations with jquery 584 | 585 | Task: you need update price on client side after price update in each product 586 | 587 | ```php 588 | 589 | ... 590 | 591 | $product = Product::model()->findByPk($productId); 592 | if ($product) { 593 | $product->price = $newPrice; 594 | if ($product->save()) { 595 | $jFrame = Yii::app()->nodeSocket->getFrameFactory()->createJQueryFrame(); 596 | $jFrame 597 | ->createQuery('#product' . $product->id) 598 | ->find('span.price') 599 | ->text($product->price); 600 | $jFrame->send(); 601 | // and all connected clients will can see updated price 602 | } 603 | } 604 | 605 | ... 606 | 607 | ``` 608 | 609 | ####Send more than one frame per a time 610 | 611 | Example 1: 612 | 613 | ```php 614 | 615 | $multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); 616 | 617 | $eventFrame = Yii::app()->nodeSocket->getFrameFactory()->createEventFrame(); 618 | 619 | $eventFrame->setEventName('updateBoard'); 620 | $eventFrame['boardId'] = 25; 621 | $eventFrame['boardData'] = $html; 622 | 623 | $dataEvent = Yii::app()->nodeSocket->getFrameFactory()->createPublicDataFrame(); 624 | 625 | $dataEvent->setKey('error.strings'); 626 | $dataEvent['key'] = $value; 627 | 628 | $multipleFrame->addFrame($eventFrame); 629 | $multipleFrame->addFrame($dataEvent); 630 | $multipleFrame->send(); 631 | 632 | ``` 633 | 634 | Example 2: 635 | 636 | ```php 637 | 638 | $multipleFrame = Yii::app()->nodeSocket->getFrameFactory()->createMultipleFrame(); 639 | 640 | $eventFrame = $multipleFrame->createEventFrame(); 641 | 642 | $eventFrame->setEventName('updateBoard'); 643 | $eventFrame['boardId'] = 25; 644 | $eventFrame['boardData'] = $html; 645 | 646 | $dataEvent = $multipleFrame->createPublicDataFrame(); 647 | 648 | $dataEvent->setKey('error.strings'); 649 | $dataEvent['key'] = $value; 650 | 651 | $multipleFrame->send(); 652 | 653 | ``` 654 | 655 | ##PS 656 | 657 | Sorry for my english :) 658 | 659 | -------------------------------------------------------------------------------- /README_RUS.md: -------------------------------------------------------------------------------- 1 | Yii Node Socket 2 | ================= 3 | 4 | Реализует связь между php и javascript по средству сокет соединения. 5 | Сокет сервер реализован на nodejs (socket.io library http://socket.io/). 6 | 7 | #Установка 8 | 9 | Установите nodejs, если не установлено (в этом вам поможет http://nodejs.org/)
10 | Установка расширения 11 | 12 | * Клонирование 13 | 14 | ```bash 15 | $> git clone git@github.com:oncesk/yii-node-socket.git 16 | ``` 17 | * В качестве submodule
18 | 19 | > ext_directory - директория куда следует установить сабмодуль 20 | 21 | ```bash 22 | $> git submodule add git@github.com:oncesk/yii-node-socket.git ext_directory 23 | $> git submodule update 24 | ``` 25 | Зайдите в директорию установленного расширения ***application.ext.yii-node-socket*** и выполнить
26 | ```bash 27 | $> git submodule init 28 | $> git submodule update 29 | ``` 30 | 31 | Настраиваем Yii
32 | * Добавить путь к консольной команде в конфиг (***main/console.php***). Сделать это можно добавивь следующие строки: 33 | 34 | ```php 35 | 'commandMap' => array( 36 | 'node-socket' => 'application.extensions.yii-node-socket.lib.php.NodeSocketCommand' 37 | ) 38 | ``` 39 | 40 | * Регистрация в компонентах Yii, добавив в **main.php и console.php**: 41 | 42 | ```php 43 | 'nodeSocket' => array( 44 | 'class' => 'application.extensions.yii-node-socket.lib.php.NodeSocket', 45 | 'host' => 'localhost', // по умолчанию 127.0.0.1, может быть как ip так и доменом, только без http 46 | 'port' => 3001 // по умолчанию 3001, должен быть целочисленным integer-ом 47 | ) 48 | ``` 49 | > Notice: ***host*** - имя домена, как в вашем виртуальном хосте либо айпи адресс сервера, если вы обращаетесь к серверу через айпи адресс 50 | 51 | Установим компоненты ***nodejs*** в директории ***application.ext.yii-node-socket.lib.js.server***: 52 | ```bash 53 | $> npm install 54 | ``` 55 | 56 | На этом установка окончена! 57 | 58 | > Обратите внимание на то, что если название компонента будет отличным от **nodeSocket**, то придется передавать название компонента в команду используя ключ --componentName=название_компонента 59 | 60 | ###Запуск сервера 61 | 62 | Сервер запускается консольной коммандой Yii (**./yiic node-socket**) 63 | 64 | ```bash 65 | $> ./yiic node-socket # выведет хелп 66 | $> ./yiic node-socket start # запуска сервера 67 | $> ./yiic node-socket stop # остановка сервера 68 | $> ./yiic node-socket restart # рестарт сервера 69 | $> ./yiic node-socket getPid # выведет pid nodejs процесса 70 | ``` 71 | 72 | ##Javascript 73 | 74 | Перед работой необходимо зарегестировать скрипты клиента на необходимой нам странице 75 | 76 | ```php 77 | public function actionIndex() { 78 | // регестрируем скрипты клиенат 79 | Yii::app()->nodeSocket->registerClientScripts(); 80 | 81 | // выполнение других действий 82 | } 83 | ``` 84 | 85 | ###События 86 | 87 | Предустановленные события: 88 | 89 | * `listener.on('connect', function () {})` - "connect" is emitted when the socket connected successfully 90 | * `listener.on('reconnect', function () {})` - "reconnect" is emitted when socket.io successfully reconnected to the server 91 | * `listener.on('disconnect', function () {})` - "disconnect" is emitted when the socket disconnected 92 | 93 | Созднаия своего слушателя события: 94 | 95 | * `listener.on('update', function (data) {})` - emitted when PHP server emit update event 96 | * `listener.on('some_event', function (data) {})` - emitted when PHP server emit some_event event 97 | 98 | ###Работа в javascript 99 | 100 | Работа с расширением происходить через класс `YiiNodeSocket` 101 | 102 | ####Начало работы 103 | 104 | ```javascript 105 | 106 | // создаем обьект для работы с библиотекой 107 | var socket = new YiiNodeSocket(); 108 | 109 | // включение режима отладки 110 | socket.debug(true); 111 | ``` 112 | 113 | ####События 114 | 115 | Сейчас события можно создавать только из PHP. Данные которые приходят в javascript передаются в формате json. 116 | В javascript данные приходят уже в обычном виде (обьекте) 117 | 118 | ```javascript 119 | // установка обработчика события 120 | socket.on('updateBoard', function (data) { 121 | // обновляем доску, выполняем какие либо действия 122 | }); 123 | ``` 124 | 125 | ####Подключение к комнате 126 | 127 | ```javascript 128 | socket.room('testRoom').join(function (success, numberOfRoomSubscribers) { 129 | // success - boolean, если при добавлении сокета произошла ошибка 130 | // то success = false, и numberOfRoomSubscribers - содержит описание ошибки 131 | // иначе numberOfRoomSubscribers - number, содержит количество клиентов в комнате 132 | if (success) { 133 | console.log(numberOfRoomSubscribers + ' clients in room: ' + roomId); 134 | // do something 135 | 136 | // bind events 137 | this.on('join', function (newMembersCount) { 138 | // fire on client join 139 | }); 140 | 141 | this.on('data', function (data) { 142 | // fire when server send frame into this room with 'data' event 143 | }); 144 | } else { 145 | // numberOfRoomSubscribers - error message 146 | alert(numberOfRoomSubscribers); 147 | } 148 | }); 149 | ``` 150 | 151 | ####Общие публичные данные 152 | 153 | Данные можно установить только из PHP используя PublicData фрейм (смотрите ниже), получить данные можно используя метод `getPublicData(string key, callback fn)` 154 | 155 | ```javascript 156 | socket.getPublicData('error.strings', function (strings) { 157 | // проверка обязательна, так как данные могут быть неустановлены, 158 | // либо истек срок хранения 159 | if (strings) { 160 | // делаем что-либо 161 | } 162 | }); 163 | ``` 164 | 165 | 166 | ##PHP 167 | 168 | ####Регистрация клиентских скриптов 169 | 170 | ```php 171 | public function actionIndex() { 172 | // регестрируем скрипты клиенат 173 | Yii::app()->nodeSocket->registerClientScripts(); 174 | 175 | // выполнение других действий 176 | } 177 | ``` 178 | 179 | ####Создание и отправка события с данными 180 | 181 | ```php 182 | 183 | ... 184 | 185 | // создаем фрейм 186 | $frame = Yii::app()->nodeSocket->createEventFrame(); 187 | $frame->setEventName('updateBoard'); 188 | $frame['boardId'] = 25; 189 | $frame['boardData'] = $html; 190 | $frame->send(); 191 | 192 | ... 193 | 194 | ``` 195 | 196 | ####Установка общих публичных данных 197 | 198 | Данные могут устанавливаться на какоето время, для установки времени жизни необходимо воспользоваться методом ***setLifeTime(integer $lifetime)*** класса PublicData 199 | 200 | ```php 201 | 202 | ... 203 | 204 | // создаем фрейм 205 | $frame = Yii::app()->nodeSocket->createPublicDataFrame(); 206 | 207 | // устанавливаем ключ в хранилище 208 | $frame->setKey('error.strings'); 209 | 210 | // записываем данные 211 | $frame->setData($errorStrings); 212 | 213 | // можно устанавливать с помощью интерфейса ArrayAccess 214 | // $frame['empty_name'] = 'Пожалуйста введите имя пользователя'; 215 | 216 | // устанавливаем время жизни 217 | $frame->setLifeTime(3600*2); // через два часа данные будут недоступны для использования 218 | 219 | // отправляем 220 | $frame->send(); 221 | 222 | ... 223 | 224 | ``` 225 | 226 | ####Отправка события в комнату 227 | 228 | ```php 229 | 230 | ... 231 | 232 | // создаем фрейм 233 | $frame = Yii::app()->nodeSocket->createEventFrame(); 234 | 235 | // устанавливаем ключ в хранилище 236 | $frame->setEventName('updateBoard'); 237 | 238 | // записываем данные 239 | $frame->setRoom('testRoom'); 240 | 241 | // устанавливаем данные 242 | 243 | $frame['key'] = $value; 244 | 245 | // отправляем 246 | $frame->send(); 247 | 248 | ... 249 | 250 | ``` 251 | 252 | Событие смогут отловить клиенты, которые состоят в комнате testRoom 253 | 254 | ####Вызов функции или метода обьекта в контексте window 255 | 256 | ```php 257 | $invokeFrame = Yii::app()->nodeSocket->createInvokeFrame(); 258 | $invokeFrame->invokeFunction('alert', array('Hello world')); 259 | $invokeFrame->send(); // выполнится у всех клиентов 260 | ``` 261 | 262 | Является наследником Event, что позволяет отправить событие в комнату 263 | 264 | ####Отправка нескольких событий за один раз 265 | 266 | Для отправки нескольких событий используйте 267 | 268 | ```php 269 | 270 | $multipleFrame = Yii::app()->nodeSocket->createMultipleFrame(); 271 | 272 | $eventFrame = Yii::app()->nodeSocket->createEventFrame(); 273 | 274 | $eventFrame->setEventName('updateBoard'); 275 | $eventFrame['boardId'] = 25; 276 | $eventFrame['boardData'] = $html; 277 | 278 | $dataEvent = Yii::app()->nodeSocket->createPublicDataFrame(); 279 | 280 | $dataEvent->setKey('error.strings'); 281 | $dataEvent['key'] = $value; 282 | 283 | $multipleFrame->addFrame($eventFrame); 284 | $multipleFrame->addFrame($dataEvent); 285 | $multipleFrame->send(); 286 | 287 | ``` 288 | 289 | Можно и так 290 | 291 | ```php 292 | 293 | $multipleFrame = Yii::app()->nodeSocket->createMultipleFrame(); 294 | 295 | $eventFrame = $multipleFrame->createEventFrame(); 296 | 297 | $eventFrame->setEventName('updateBoard'); 298 | $eventFrame['boardId'] = 25; 299 | $eventFrame['boardData'] = $html; 300 | 301 | $dataEvent = $multipleFrame->createPublicDataFrame(); 302 | 303 | $dataEvent->setKey('error.strings'); 304 | $dataEvent['key'] = $value; 305 | 306 | $multipleFrame->send(); 307 | 308 | ``` 309 | 310 | ##В планах 311 | 312 | 1. Создать систему subscribe/unsibscribe, с созданием, удалением, сохранением каналов, добавления и удаления подписчиков 313 | 2. Хранение информации и канале и подписчиках в базе данных, скорее всего mongoDB 314 | 3. Возможность создавать каналы с разными правами (публичный канал, публичный ограниченный, приватный, приватный ограниченный) 315 | 4. Аутентификация сокета 316 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oncesk/yii-node-socket", 3 | "description": "Реализует связь между php и client javascript по средству сокет соединения.", 4 | "minimum-stability": "stable", 5 | "authors": [ 6 | { 7 | "name": "Alexey Panasik", 8 | "email": "unnfly@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "oncesk/elephant.io": "dev-master" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "YiiNodeSocket\\": "lib/php/" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/events/controllers/NsEventExampleController.php: -------------------------------------------------------------------------------- 1 | render('index'); 6 | } 7 | 8 | public function actionSendEvent() { 9 | $event = Yii::app()->nodeSocket->createEventFrame(); 10 | $event->setEventName('event.example'); 11 | $event['data'] = array( 12 | 1, 13 | array( 14 | 'red', 15 | 'black', 16 | 'white' 17 | ), 18 | new stdClass(), 19 | 'simple string' 20 | ); 21 | $event->send(); 22 | $this->render('sendEvent'); 23 | } 24 | 25 | public function actionSendRoomEvent() { 26 | $event = Yii::app()->nodeSocket->createEventFrame(); 27 | 28 | $event->setRoom('example'); 29 | $event->setEventName('example.room.event'); 30 | 31 | $event['type_string'] = 'hello world'; 32 | $event['type_array'] = array(1, 2, 3); 33 | $event['type_object'] = array('one' => 1, 'two' => 2); 34 | $event['type_bool'] = true; 35 | $event['type_integer'] = 11; 36 | 37 | $event->send(); 38 | $this->render('sendRoomEvent'); 39 | } 40 | 41 | public function actionEventListener() { 42 | Yii::app()->nodeSocket->registerClientScripts(); 43 | $this->render('eventListener'); 44 | } 45 | } -------------------------------------------------------------------------------- /examples/events/views/eventListener.php: -------------------------------------------------------------------------------- 1 | 10 |
11 | -------------------------------------------------------------------------------- /examples/events/views/index.php: -------------------------------------------------------------------------------- 1 | 6 |

Instruction

7 | Don't forget run nodejs server ./yiic node-socket/start before example usage 8 | 12 | 17 | -------------------------------------------------------------------------------- /examples/events/views/sendEvent.php: -------------------------------------------------------------------------------- 1 |

Event successfully sent

2 | Resend event -------------------------------------------------------------------------------- /examples/events/views/sendRoomEvent.php: -------------------------------------------------------------------------------- 1 |

Event successfully sent

2 | Resend event -------------------------------------------------------------------------------- /lib/js/client/client.template.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var rooms = {}; 4 | var channels = {}; 5 | var aliases = {}; 6 | var socket; 7 | 8 | /** 9 | * Log manager 10 | * 11 | * @constructor 12 | */ 13 | function Logger() { 14 | 15 | this.isDebugMode = true; 16 | 17 | var console = window.console || { 18 | info : function () {}, 19 | log : function () {}, 20 | error : function () {} 21 | }; 22 | 23 | if (!console['log']) { 24 | console.log = function () {} 25 | } 26 | 27 | if (!console['error']) { 28 | console.error = function () {} 29 | } 30 | 31 | if (!console['info']) { 32 | console.info = function () {} 33 | } 34 | 35 | this.log = function (message) { 36 | if (this.isDebugMode) { 37 | console.log(new Date().getTime() + ': ' + message); 38 | } 39 | }; 40 | 41 | this.info = function () { 42 | if (this.isDebugMode) { 43 | console.info.apply(console, arguments); 44 | } 45 | }; 46 | 47 | this.error = function () { 48 | if (this.isDebugMode) { 49 | console.error.apply(console, arguments); 50 | } 51 | }; 52 | } 53 | 54 | var logger = new Logger(); 55 | 56 | /** 57 | * Hooked event listen manager 58 | * 59 | * @param scope 60 | * @constructor 61 | */ 62 | function EventHookManager(scope) { 63 | 64 | var events = { 65 | before : {}, 66 | after : {} 67 | }; 68 | 69 | this.trigger = function (when, event, data) { 70 | logger.log('Triggered hook manager, when - ' + when + ', event - ' + event); 71 | if (events[when] && events[when][event]) { 72 | for (var i in events[when][event]) { 73 | var executionResult = events[when][event][i].call(scope, data, event); 74 | if (executionResult === false) { 75 | return false; 76 | break; 77 | } 78 | } 79 | } 80 | return true; 81 | }; 82 | 83 | /** 84 | * 85 | * @param event 86 | * @param fn 87 | * @returns {*} 88 | */ 89 | this.before = function (event, fn) { 90 | if (!events.before[event]) { 91 | events.before[event] = []; 92 | } 93 | logger.log('Add new hook: when - before, event - ' + event); 94 | events.before[event].push(fn); 95 | return this; 96 | }; 97 | 98 | /** 99 | * 100 | * @returns {*} 101 | */ 102 | this.after = function (event, fn) { 103 | if (!events.after[event]) { 104 | events.after[event] = []; 105 | } 106 | logger.log('Add new hook: when - after, event - ' + event); 107 | events.after[event].push(fn); 108 | return this; 109 | }; 110 | 111 | var self = this; 112 | 113 | scope.after = function () { 114 | self.after.apply(self, arguments); 115 | }; 116 | scope.before = function () { 117 | self.before.apply(self, arguments); 118 | }; 119 | } 120 | 121 | function EventEmitter(owner, scope) { 122 | 123 | logger.log( 124 | 'Creating new event emitter with scope ' + 125 | scope.type + 126 | (scope.type == 'global' ? '' : ':' + scope.id) 127 | ); 128 | 129 | var self = this; 130 | self.broadcast = false; 131 | 132 | /** 133 | * 134 | * @param event 135 | * @param data 136 | */ 137 | owner.emit = function (event, data) { 138 | logger.log('Emit event `' + event + '` in scope ' + scope.type + (scope.type == 'global' ? '' : ':' + scope.id)); 139 | socket.emit('event:emit', { 140 | 'event' : event, 141 | 'scope' : scope, 142 | 'data' : data, 143 | 'broadcast' : self.broadcast 144 | }); 145 | self.broadcast = false; 146 | }; 147 | 148 | } 149 | 150 | /** 151 | * Event listen manager 152 | * 153 | * @param {object} owner 154 | * @param eventPrefix 155 | * @constructor 156 | */ 157 | function EventListener(owner, eventPrefix) { 158 | 159 | /** 160 | * Add before event 161 | * 162 | * @name before 163 | * @function 164 | * @memberOf EventHookManager 165 | */ 166 | 167 | /** 168 | * Add after event 169 | * 170 | * @name after 171 | * @function 172 | * @memberOf EventHookManager 173 | */ 174 | 175 | eventPrefix = eventPrefix || ''; 176 | var self = this; 177 | var eventHookListener = new EventHookManager(owner); 178 | var events = {}; 179 | 180 | /** 181 | * 182 | * @returns {Object} 183 | */ 184 | this.getOwner = function () { 185 | return owner || this; 186 | }; 187 | 188 | /** 189 | * 190 | * @returns {*} 191 | */ 192 | this.getId = function () { 193 | if (this.getOwner() === this) { 194 | return ''; 195 | } 196 | return this.getOwner().getId(); 197 | }; 198 | 199 | this.getHookListener = function () { 200 | return eventHookListener; 201 | }; 202 | 203 | this.setEventPrefix = function (prefix) { 204 | logger.log('Set event prefix to: ' + prefix); 205 | eventPrefix = prefix; 206 | }; 207 | 208 | this.emit = function (event, data) { 209 | logger.log('Tying to fire event: ' + event); 210 | if (eventHookListener.trigger('before', event, data)) { 211 | if (events[event]) { 212 | var chain = { 213 | 'break' : false 214 | }; 215 | for (var i in events[event]) { 216 | events[event][i].call(owner, data, chain); 217 | if (chain['break'] === true) { 218 | logger.log('Handler of event: ' + event + ' break event chain'); 219 | break; 220 | } 221 | } 222 | } else { 223 | logger.log('Event [' + event + '] has no listeners'); 224 | } 225 | eventHookListener.trigger('after', event, data); 226 | } 227 | logger.log('Event [' + event + '] processed!'); 228 | return owner; 229 | }; 230 | 231 | var getInternalEventName = function (event) { 232 | var id = self.getId(); 233 | if (id == '') { 234 | return eventPrefix + ':' + event; 235 | } 236 | return eventPrefix + id + ':' + event; 237 | }; 238 | 239 | /** 240 | * 241 | * @param event 242 | * @param fn 243 | * @returns {*} 244 | */ 245 | this.on = function (event, fn) { 246 | logger.log('Attach event listener for event: ' + event); 247 | if (!events[event]) { 248 | events[event] = []; 249 | } 250 | events[event].push(fn); 251 | var self = this; 252 | var ev = getInternalEventName(event); 253 | logger.log('Compiled event name: ' + ev); 254 | socket.on(ev, function (data) { 255 | logger.log('Received data for event: ' + event); 256 | self.emit(event, data); 257 | }); 258 | return owner; 259 | }; 260 | 261 | /** 262 | * 263 | * @param event 264 | * @param fn 265 | * @returns {*} 266 | */ 267 | owner.on = function (event, fn) { 268 | self.on(event, fn); 269 | }; 270 | } 271 | 272 | function SystemEventHandlers() { 273 | 274 | var self = this; 275 | 276 | var handlers = { 277 | 'invoke' : function (frame) { 278 | if (frame['functions'] && typeof frame['functions'] == 'object') { 279 | for (var i in frame['functions']) { 280 | var func = frame['functions'][i]['function']; 281 | var args = frame['functions'][i]['arguments']; 282 | var scope = frame['functions'][i]['scope']; 283 | if (window[func] && typeof window[func] == 'function') { 284 | if (scope && window[scope]) { 285 | window[func].apply(window[scope], args); 286 | } else { 287 | window[func].apply(window, args); 288 | } 289 | } 290 | } 291 | } 292 | if (frame['methods'] && typeof frame['methods'] == 'object') { 293 | for (var i in frame['methods']) { 294 | var func = frame['methods'][i]['method']; 295 | var args = frame['methods'][i]['arguments']; 296 | var scope = frame['methods'][i]['scope']; 297 | var obj = frame['methods'][i]['object']; 298 | if (window[obj] && typeof window[obj] == 'object') { 299 | if (typeof window[obj][func] == 'function') { 300 | if (scope && typeof window[scope] == 'object') { 301 | window[obj][func].apply(window[scope], args); 302 | } else { 303 | window[obj][func].apply(window[obj], args); 304 | } 305 | } 306 | } 307 | } 308 | } 309 | }, 310 | 311 | 'jquery' : function (queries) { 312 | for (var i in queries) { 313 | var query = queries[i]; 314 | if (query.beforeApply && window[query.beforeApply]) { 315 | if (window[query.beforeApply]() === false) { 316 | continue; 317 | } 318 | } 319 | $(query.selector).is(function () { 320 | var $this = $(this); 321 | for (var i in query.actions) { 322 | var action = query.actions[i].action; 323 | var args = query.actions[i].arguments; 324 | switch (action) { 325 | 326 | case 'remove': 327 | $this.remove(); 328 | return; 329 | break; 330 | 331 | case 'parents': 332 | case 'find': 333 | case 'parent': 334 | case 'next': 335 | case 'prev': 336 | case 'children': 337 | $this = $this[action].apply($this, args); 338 | break; 339 | 340 | default: 341 | $this[action].apply($this, args); 342 | break; 343 | } 344 | } 345 | }); 346 | if (query.afterApply && window[query.afterApply]) { 347 | window[query.afterApply](); 348 | } 349 | } 350 | } 351 | }; 352 | 353 | var bind = function (event, handler, binder) { 354 | binder.on(event, function (data) { 355 | logger.log('Catch system event ' + event); 356 | handlers[event].call(this, data); 357 | }); 358 | }; 359 | 360 | this.attachTo = function (binder) { 361 | for (var event in handlers) { 362 | bind(event, handlers[event], binder); 363 | } 364 | }; 365 | } 366 | 367 | 368 | 369 | function SystemEventsHandlerBinder(owner) { 370 | 371 | var systemEventHandlers = new SystemEventHandlers(); 372 | 373 | /** 374 | * Attach event handler event 375 | * 376 | * @name on 377 | * @function 378 | * @memberOf EventListener 379 | */ 380 | 381 | var eventListener = new EventListener(this); 382 | 383 | /** 384 | * 385 | * @returns {string} 386 | */ 387 | this.getId = function () { 388 | return 'system'; 389 | }; 390 | 391 | /** 392 | * 393 | * @returns {*} 394 | */ 395 | this.getOwner = function () { 396 | return owner; 397 | }; 398 | 399 | systemEventHandlers.attachTo(this); 400 | } 401 | 402 | /** 403 | * Room object needed for joining into some room and catch room events 404 | * 405 | * @param id 406 | * @constructor 407 | */ 408 | function Room(id) { 409 | 410 | /** 411 | * Emit event 412 | * 413 | * @name emit 414 | * @function 415 | * @memberOf EventEmitter 416 | */ 417 | 418 | /** 419 | * Add before event 420 | * 421 | * @name before 422 | * @function 423 | * @memberOf EventHookManager 424 | */ 425 | 426 | /** 427 | * Add after event 428 | * 429 | * @name after 430 | * @function 431 | * @memberOf EventHookManager 432 | */ 433 | 434 | /** 435 | * Attach event handler event 436 | * 437 | * @name on 438 | * @function 439 | * @memberOf EventListener 440 | */ 441 | 442 | this._eventListener = new EventListener(this, 'room:'); 443 | this._eventEmitter = new EventEmitter(this, { 444 | 'type' : 'room', 445 | 'id' : id 446 | }); 447 | // var systemEventsHandlerBinder = new SystemEventsHandlerBinder(this); 448 | 449 | this._isJoined = false; 450 | this._numberOfClients = 0; 451 | this._error = false; 452 | this._errorMessage = ''; 453 | 454 | this.getId = function () { 455 | return id; 456 | }; 457 | 458 | this.getMembersCount = function () { 459 | return this._numberOfClients; 460 | }; 461 | 462 | this.hasError = function () { 463 | return this._error; 464 | }; 465 | 466 | this.isJoined = function () { 467 | return this._isJoined; 468 | }; 469 | 470 | /** 471 | * Update members count if some socket join to room 472 | */ 473 | this.on('system:update.members_count', function (newMembersCount) { 474 | this._numberOfClients = newMembersCount; 475 | this._eventListener.emit('join', newMembersCount) 476 | }); 477 | 478 | this.join = function (fn) { 479 | if (this.isJoined()) { 480 | if (fn) { 481 | fn.call(this, this.isJoined(), this.getMembersCount()); 482 | } 483 | return; 484 | } 485 | var self = this; 486 | logger.log('Tying to join in room ' + self.getId()); 487 | socket.emit('room_join', self.getId(), function (isJoined, numberOfClientsInRoom) { 488 | self._isJoined = isJoined; 489 | if (isJoined) { 490 | logger.log('Joined in room ' + self.getId()); 491 | logger.log('Room members count ' + numberOfClientsInRoom); 492 | self._numberOfClients = numberOfClientsInRoom; 493 | } else { 494 | self._error = true; 495 | self._errorMessage = numberOfClientsInRoom; 496 | logger.log('Room join error: room - ' + self.getId() + ', reason - ' + numberOfClientsInRoom); 497 | } 498 | if (fn) { 499 | fn.call(self, isJoined, numberOfClientsInRoom); 500 | } 501 | }); 502 | return this; 503 | }; 504 | 505 | this.leave = function (fn) { 506 | if (!fn) { 507 | fn = function () {}; 508 | } 509 | if (!this.isJoined()) { 510 | fn.call(this); 511 | return; 512 | } 513 | var self = this; 514 | socket.emit('room_leave', this.getId(), function () { 515 | self._isJoined = false; 516 | fn.call(self); 517 | }); 518 | return this; 519 | }; 520 | } 521 | 522 | function ChannelAlias(alias) { 523 | 524 | this._eventListener = new EventListener(this, 'alias:'); 525 | // var systemEventsHandlerBinder = new SystemEventsHandlerBinder(this); 526 | 527 | this.getId = function () { 528 | return alias; 529 | }; 530 | } 531 | 532 | function Channel(id) { 533 | 534 | /** 535 | * Emit event 536 | * 537 | * @name emit 538 | * @function 539 | * @memberOf EventEmitter 540 | */ 541 | 542 | this.id = id; 543 | 544 | this._eventListener = new EventListener(this, 'channel:'); 545 | this._eventEmitter = new EventEmitter(this, { 546 | 'type' : 'channel', 547 | 'id' : this.id 548 | }); 549 | // var systemEventsHandlerBinder = new SystemEventsHandlerBinder(this); 550 | var isJoined = false; 551 | var attributes = {}; 552 | var error = {}; 553 | 554 | this.getId = function () { 555 | return id; 556 | }; 557 | 558 | this.getAttributes = function () { 559 | return attributes; 560 | }; 561 | 562 | this.getError = function () { 563 | return error; 564 | }; 565 | 566 | this.isConnected = function () { 567 | return isJoined; 568 | }; 569 | 570 | this.join = function (fn) { 571 | if (isJoined) { 572 | fn.call(this, false); 573 | return; 574 | } 575 | var self = this; 576 | logger.log('Tying to join to channel ' + this.getId()); 577 | socket.on('connect',function(){ 578 | socket.emit('channel_join', self.getId(), function (data, err) { 579 | isJoined = !err; 580 | if (err) { 581 | logger.log('Channel join error - ' + self.getId() + ', reason - ' + data.errorMessage); 582 | error = data; 583 | } else { 584 | attributes = data; 585 | } 586 | if (fn) { 587 | fn.call(self, !err); 588 | } 589 | }); 590 | }); 591 | 592 | return this; 593 | }; 594 | 595 | this.leave = function (fn) { 596 | if (!fn) { 597 | fn = function () {}; 598 | } 599 | if (!this.isConnected()) { 600 | fn.call(this); 601 | return; 602 | } 603 | var self = this; 604 | socket.emit('channel_leave', self.getId(), function () { 605 | isJoined = false; 606 | fn.call(self); 607 | }); 608 | return this; 609 | }; 610 | } 611 | 612 | function YiiNodeSocket() { 613 | 614 | /** 615 | * Emit event 616 | * 617 | * @name emit 618 | * @function 619 | * @memberOf EventEmitter 620 | */ 621 | 622 | /** 623 | * Add before event 624 | * 625 | * @name before 626 | * @function 627 | * @memberOf EventHookManager 628 | */ 629 | 630 | /** 631 | * Add after event 632 | * 633 | * @name after 634 | * @function 635 | * @memberOf EventHookManager 636 | */ 637 | 638 | /** 639 | * Attach event handler event 640 | * 641 | * @name on 642 | * @function 643 | * @memberOf EventListener 644 | */ 645 | 646 | var self = this; 647 | 648 | this._eventListener = new EventListener(this, 'global'); 649 | this._eventEmitter = new EventEmitter(this, { 650 | 'type' : 'global' 651 | }); 652 | 653 | socket = io.connect('http://host;?>:port;?>/client'); 654 | 655 | var systemEventsHandlerBinder = new SystemEventsHandlerBinder(this); 656 | 657 | this.__defineGetter__('broadcast', function () { 658 | self._eventEmitter.broadcast = true; 659 | return self; 660 | }); 661 | 662 | this.getId = function () { 663 | return ''; 664 | }; 665 | 666 | this.debug = function (flag) { 667 | logger.isDebugMode = flag; 668 | return this; 669 | }; 670 | 671 | /** 672 | * 673 | * @returns {Logger} 674 | */ 675 | this.getLogger = function () { 676 | return logger; 677 | }; 678 | 679 | /** 680 | * @param {string} key 681 | * @param {function} fn 682 | */ 683 | this.getPublicData = function (key, fn) { 684 | logger.log('Trying to get public data for key: ' + key); 685 | socket.emit('public_data', key, function (data) { 686 | logger.log('Received public data for key: ' + key); 687 | if (fn) { 688 | fn(data); 689 | } 690 | }); 691 | }; 692 | 693 | /** 694 | * 695 | * @param id 696 | * @returns Room 697 | */ 698 | this.room = function (id) { 699 | if (rooms[id]) { 700 | return rooms[id]; 701 | } 702 | var room = rooms[id] = new Room(id); 703 | return room; 704 | }; 705 | 706 | /** 707 | * 708 | * @param id 709 | * @returns Channel 710 | */ 711 | this.channel = function (id) { 712 | if (channels[id]) { 713 | return channels[id]; 714 | } 715 | var channel = channels[id] = new Channel(id); 716 | return channel; 717 | }; 718 | 719 | /** 720 | * 721 | * @param alias 722 | * @returns ChannelAlias 723 | */ 724 | this.alias = function (alias) { 725 | if (aliases[alias]) { 726 | return aliases[alias]; 727 | } 728 | return aliases[alias] = new ChannelAlias(alias); 729 | }; 730 | 731 | this.onConnect = function (fn) { 732 | if (fn) { 733 | socket.on('connect', fn); 734 | } 735 | }; 736 | 737 | this.onDisconnect = function (fn) { 738 | if (fn) { 739 | socket.on('disconnect', fn); 740 | } 741 | }; 742 | 743 | this.onConnecting = function (fn) { 744 | if (fn) { 745 | socket.on('connecting', fn); 746 | } 747 | }; 748 | 749 | this.onReconnect = function (fn) { 750 | if (fn) { 751 | socket.on('reconnect', fn); 752 | } 753 | }; 754 | 755 | this.close = function () { 756 | socket.close(); 757 | }; 758 | } 759 | 760 | window.YiiNodeSocket = YiiNodeSocket; 761 | })(); 762 | -------------------------------------------------------------------------------- /lib/js/server/components/autoload/channel.manager.js: -------------------------------------------------------------------------------- 1 | var subscribers = {}; 2 | var channels = {}; 3 | var channelByNames = {}; 4 | var subscriberChannels = {}; 5 | var channelSubscribers = {}; 6 | var uidSubscriber = {}; 7 | var sidSubscriber = {}; 8 | var subscriberChannelOptions = {}; 9 | 10 | function createDBSubscriber(attributes, fn) { 11 | 12 | } 13 | 14 | function createSubscriber(attributes) { 15 | var subscriber = { 16 | id : attributes.id, 17 | uid : attributes.user_id, 18 | sid : attributes.sid, 19 | attributes : attributes, 20 | channels : {}, 21 | init : function () { 22 | subscribers[this.id] = this; 23 | if (this.uid) { 24 | uidSubscriber[this.uid] = this; 25 | } 26 | if (this.sid) { 27 | sidSubscriber[this.sid] = this; 28 | } 29 | }, 30 | delete : function () { 31 | for (var id in this.channels) { 32 | this.channels[id].unSubscribe(this); 33 | } 34 | this.channels = null; 35 | if (subscribers[this.id]) { 36 | delete subscribers[this.id]; 37 | if (this.uid && uidSubscriber[this.uid]) { 38 | delete uidSubscriber[this.uid]; 39 | } 40 | if (this.sid && sidSubscriber[this.sid]) { 41 | delete sidSubscriber[this.sid]; 42 | } 43 | } 44 | } 45 | }; 46 | subscriber.init(); 47 | return subscriber; 48 | } 49 | 50 | function createChannel(attributes, manager) { 51 | var channel = { 52 | id : attributes.id, 53 | name : attributes.name, 54 | attributes : attributes, 55 | isAllowed : function (role) { 56 | if (this.attributes.allowed_roles) { 57 | for (var i in this.attributes.allowed_roles) { 58 | if (this.attributes.allowed_roles[i] == role) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | return true; 65 | }, 66 | getAlias : function () { 67 | return this.attributes.properties['alias']; 68 | }, 69 | subscribe : function (subscriber, options, checkDbRecord, fn) { 70 | if (this.subscribers[subscriber.id]) { 71 | if (fn) { 72 | fn(true); 73 | } 74 | return; 75 | } 76 | var self = this; 77 | var options = options || {}; 78 | var subscribe = function (options) { 79 | subscriber.channels[self.id] = self; 80 | var socket; 81 | if (subscriber.uid) { 82 | socket = manager.get('sp').getByUid(subscriber.uid); 83 | } else { 84 | socket = manager.get('sp').getBySid(subscriber.sid); 85 | } 86 | self.join(socket); 87 | self.subscribersNumber++; 88 | self.subscribers[subscriber.id] = subscriber; 89 | self.emit('subscribe', subscriber.attributes); 90 | self.subscriptionOptions[subscriber.id] = options; 91 | if (fn) { 92 | fn(true); 93 | } 94 | }; 95 | if (checkDbRecord) { 96 | var subscriberChannel = manager.get('db').SubscriberChannel; 97 | // check if link between subscriber and channel exists 98 | subscriberChannel.exists({ 99 | channel_id : this.id, 100 | subscriber_id : subscriber.id 101 | }, function (exists, err) { 102 | if (err) { 103 | // error 104 | if (fn) { 105 | fn(false); 106 | } 107 | } else { 108 | if (exists) { 109 | subscribe(); 110 | fn(false, exists); 111 | } else { 112 | var link = { 113 | channel_id : self.id, 114 | subscriber_id : subscriber.id, 115 | can_send_event_from_php : 1, 116 | can_send_event_from_js : 0 117 | }; 118 | subscriberChannel.create(link, function (err) { 119 | if (err) { 120 | fn(false); 121 | } else { 122 | subscribe(link); 123 | } 124 | }); 125 | } 126 | } 127 | }); 128 | } else { 129 | subscribe(options); 130 | } 131 | }, 132 | unSubscribe : function (subscriber) { 133 | if (subscriber.channels[this.id]) { 134 | delete subscriber.channels[this.id]; 135 | } 136 | var socket; 137 | if (subscriber.attributes.uid) { 138 | socket = manager.get('sp').getByUid(subscriber.attributes.uid); 139 | } else { 140 | socket = manager.get('sp').getBySid(subscriber.attributes.sid); 141 | } 142 | this.leave(socket); 143 | this.subscribersNumber--; 144 | delete this.subscribers[subscriber.id]; 145 | this.emit('unsubscribe', subscriber.attributes); 146 | }, 147 | 148 | join : function (socket) { 149 | if (socket) { 150 | socket.join('channel:' + this.name); 151 | } 152 | }, 153 | 154 | leave : function (socket) { 155 | if (socket) { 156 | socket.leave('channel:' + this.name); 157 | } 158 | }, 159 | 160 | clientLeave : function (socket, fn) { 161 | var self = this; 162 | 163 | // try find subscriber by sid 164 | var subscriber = sidSubscriber[socket.handshake.sid]; 165 | if (!subscriber && socket.handshake.uid) { 166 | // if not found, try find by uid 167 | subscriber = uidSubscriber[socket.handshake.uid]; 168 | } 169 | if (subscriber) { 170 | this.unSubscribe(subscriber); 171 | fn(true); 172 | } else { 173 | fn(false); 174 | } 175 | }, 176 | 177 | clientJoin : function (socket, fn) { 178 | var self = this; 179 | 180 | // try find subscriber by sid 181 | var subscriber = sidSubscriber[socket.handshake.sid]; 182 | if (!subscriber && socket.handshake.uid) { 183 | // if not found, try find by uid 184 | subscriber = uidSubscriber[socket.handshake.uid]; 185 | } 186 | if (subscriber) { 187 | // if subscriber exists need to check his subscription 188 | // check if subscriber subscribed on this channel 189 | if (this.subscribers[subscriber.id]) { 190 | // already subscribed 191 | fn(this.attributes, false); 192 | return; 193 | } else if (this.attributes.subscriber_source > 1) { 194 | // if subscriber can subscribe from client side, do it! 195 | this.subscribe(subscriber); 196 | fn(this.attributes, false); 197 | return; 198 | } 199 | // seems like clients can not be subscribed from javascript 200 | fn({ 201 | errorMessage : 'Subscription is unavailable' 202 | }, true); 203 | return; 204 | } else { 205 | // seems like subscriber not created 206 | // hm, can this be? yes, if connection from javascript and subscriber was not created from php 207 | // need to check if this channel allow subscription from javascript 208 | if (this.attributes.subscriber_source == 1) { 209 | // allowed only from php 210 | fn({ 211 | errorMessage : 'Subscription is unavailable' 212 | }, true); 213 | return; 214 | } 215 | // check if authentication required 216 | if (this.attributes.is_authentication_required) { 217 | // socket should be authenticated 218 | if (!socket.handshake.session.user.isAuthenticated) { 219 | // seems like no 220 | fn({ 221 | errorMessage : 'Authentication required' 222 | }, true); 223 | return; 224 | } 225 | // need to check user role 226 | if (!this.isAllowed(socket.handshake.session.user.role)) { 227 | // socket role not allowed 228 | fn({ 229 | errorMessage : 'Subscription is unavailable' 230 | }, true); 231 | return; 232 | } 233 | } 234 | // we need to create subscriber 235 | createDBSubscriber({ 236 | user_id : socket.handshake.uid, 237 | sid : socket.handshake.sid, 238 | role : socket.handshake.session.user.role 239 | }, function (subscriber, err) { 240 | // check on error 241 | if (err) { 242 | // return error message 243 | fn({ 244 | errorMessage : subscriber 245 | }, true); 246 | return; 247 | } 248 | // successfully created 249 | // subscribe 250 | self.subscribe(subscriber); 251 | // return channel information 252 | fn(self.attributes, false); 253 | }); 254 | } 255 | }, 256 | getPull : function () { 257 | return manager 258 | .get('io') 259 | .of('/client') 260 | .in('channel:' + this.name); 261 | }, 262 | getClientEventName : function (event) { 263 | return 'channel:' + this.name + ':' + event; 264 | }, 265 | emit : function (event, data) { 266 | this.getPull().emit( 267 | this.getClientEventName(event), 268 | data 269 | ); 270 | this._sendAlias(event, data); 271 | }, 272 | broadcast : function (event, data) { 273 | this.getPull().broadcast( 274 | this.getClientEventName(event), 275 | data 276 | ); 277 | this._sendAlias(event, data); 278 | }, 279 | _sendAlias : function (event, data) { 280 | var alias = this.getAlias(); 281 | if (alias) { 282 | this.getPull().emit( 283 | 'alias:' + alias + ':' + event, 284 | { 285 | sender : this.attributes, 286 | data : data 287 | } 288 | ); 289 | } 290 | }, 291 | subscribers : {}, 292 | subscriptionOptions : {}, 293 | subscribersNumber : 0, 294 | init : function () { 295 | channels[this.id] = this; 296 | channelByNames[this.name] = this; 297 | 298 | if (this.attributes.allowed_roles) { 299 | this.attributes.allowed_roles = this.attributes.allowed_roles.split(','); 300 | } 301 | }, 302 | delete : function () { 303 | for (var id in this.subscribers) { 304 | this.unSubscribe(this.subscribers[id]); 305 | } 306 | this.subscribers = null; 307 | delete channels[this.id]; 308 | delete channelByNames[this.name]; 309 | } 310 | }; 311 | channel.init(); 312 | return channel; 313 | } 314 | 315 | 316 | var manager = { 317 | 318 | manager : null, 319 | name : 'channel', 320 | init : function () {}, 321 | 322 | channel : { 323 | 324 | get : function (id) { 325 | return channels[id]; 326 | }, 327 | 328 | getByName : function (name) { 329 | return channelByNames[name]; 330 | }, 331 | 332 | create : function (attributes) { 333 | return createChannel(attributes, manager.manager); 334 | }, 335 | 336 | delete : function (frame) { 337 | var channel = this.get(frame.data.id); 338 | if (channel) { 339 | channel.delete(); 340 | } 341 | } 342 | 343 | }, 344 | subscriber : { 345 | get : function (id) { 346 | return subscribers[id]; 347 | }, 348 | 349 | getBySid : function (sid) { 350 | return sidSubscriber[sid]; 351 | }, 352 | 353 | getByUid : function (uid) { 354 | return uidSubscriber[uid]; 355 | }, 356 | 357 | create : function (attributes) { 358 | return createSubscriber(attributes, manager.manager); 359 | }, 360 | 361 | delete : function (frame) { 362 | var subscriber = this.get(frame.data.id); 363 | if (subscriber) { 364 | subscriber.delete(); 365 | } 366 | } 367 | }, 368 | 369 | attachToChannels : function (socket) { 370 | var subscriber = this.subscriber.getBySid(socket.handshake.sid); 371 | if (!subscriber && socket.handshake.uid) { 372 | subscriber = this.subscriber.getByUid(socket.handshake.uid); 373 | } 374 | if (subscriber) { 375 | // if subscriber found 376 | // join it to subscribed channels 377 | for (var id in subscriber.channels) { 378 | subscriber.channels[id].join(socket); 379 | } 380 | } 381 | } 382 | }; 383 | 384 | module.exports = manager; -------------------------------------------------------------------------------- /lib/js/server/components/autoload/public.data.js: -------------------------------------------------------------------------------- 1 | var data = {}; 2 | module.exports = { 3 | 4 | manager : null, 5 | 6 | name : 'publicData', 7 | 8 | init : function () {}, 9 | 10 | get : function (key) { 11 | return data[key]; 12 | }, 13 | 14 | set : function (key, value, lifetime) { 15 | data[key] = value; 16 | if (lifetime > 0) { 17 | setTimeout(function () { 18 | if (data[key]) { 19 | delete data[key]; 20 | } 21 | }, lifetime * 1000); 22 | } 23 | } 24 | }; -------------------------------------------------------------------------------- /lib/js/server/components/component.manager.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var events = require("events"); 3 | var util = require("util"); 4 | var components = {}; 5 | 6 | function Manager() { 7 | events.EventEmitter.call(this); 8 | } 9 | util.inherits(Manager, events.EventEmitter); 10 | 11 | Manager.prototype.set = function (name, component) { 12 | components[name] = component; 13 | component.cm = this; 14 | if (component['onComponentMangerSet']) { 15 | component['onComponentMangerSet'](); 16 | } 17 | this.emit('set.component', name); 18 | return this; 19 | }; 20 | 21 | Manager.prototype.get = function (name) { 22 | return components[name]; 23 | }; 24 | 25 | Manager.prototype.initCompleted = function () { 26 | this.emit('init.completed'); 27 | }; 28 | 29 | var manager = new Manager(); 30 | var files = fs.readdirSync( __dirname + '/autoload/' ); 31 | for (var i in files) { 32 | var component = require('./autoload/' + files[i]); 33 | if (component && component.name) { 34 | component.manager = manager; 35 | component.init(); 36 | manager.set(component.name, component); 37 | } 38 | } 39 | 40 | module.exports = manager; -------------------------------------------------------------------------------- /lib/js/server/components/db.js: -------------------------------------------------------------------------------- 1 | var db = { 2 | 3 | cm : null, 4 | 5 | _dbOptions : {}, 6 | _driver : null, 7 | 8 | _createDriver : function () { 9 | var driver = require('./db/' + this._dbOptions['driver'] + '/connector.js'); 10 | if (driver.init(this._dbOptions)) { 11 | this._driver = driver; 12 | return true; 13 | } 14 | return false; 15 | }, 16 | 17 | _setUp : function () { 18 | var self = this; 19 | 20 | var processLimit = 10; 21 | 22 | var loadSubscribers = function (channels) { 23 | 24 | }; 25 | 26 | this.getDriver().channel.count(function (err, count) { 27 | if (!err) { 28 | var iterations = Math.ceil(count/processLimit); 29 | var offset = 0; 30 | 31 | var setUp = function () { 32 | if (iterations == 0) { 33 | return; 34 | } 35 | self.getDriver().channel.get(function (err, data) { 36 | if (!err) { 37 | loadSubscribers(data, function () { 38 | offset += processLimit; 39 | iterations--; 40 | process.nextTick(function () { 41 | setUp(); 42 | }); 43 | }); 44 | } 45 | }, {}, offset, processLimit); 46 | }; 47 | setUp(); 48 | } 49 | }); 50 | }, 51 | 52 | onComponentMangerSet : function () { 53 | var self = this; 54 | this.cm.on('init.completed', function () { 55 | self._setUp(); 56 | }); 57 | }, 58 | 59 | init : function (dbOptions) { 60 | this._dbOptions = dbOptions; 61 | }, 62 | 63 | getDriver : function () { 64 | if (!this._driver) { 65 | this._createDriver(); 66 | } 67 | return this._driver; 68 | } 69 | }; 70 | 71 | module.exports = db; -------------------------------------------------------------------------------- /lib/js/server/components/db/dummy/connector.js: -------------------------------------------------------------------------------- 1 | var connector = { 2 | 3 | init : function (options) { 4 | return true; 5 | }, 6 | 7 | channel : { 8 | count : function (fn) { 9 | fn('This is a dummy connector'); 10 | }, 11 | 12 | get : function (fn) { 13 | fn('This is a dummy connector'); 14 | } 15 | }, 16 | subscriber : { 17 | 18 | }, 19 | channelSubscriber : { 20 | 21 | } 22 | }; 23 | module.exports = connector; -------------------------------------------------------------------------------- /lib/js/server/components/db/mysql/connector.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | function Model(table) { 3 | this.table = table; 4 | this.connector = null; 5 | } 6 | Model.prototype.setConnector = function (connector) { 7 | this.connector = connector; 8 | }; 9 | Model.prototype.findByPk = function (pk, fn) { 10 | var self = this; 11 | if (typeof pk == 'string' || typeof pk == 'number') { 12 | pk = [pk]; 13 | } 14 | this.connector.getConnection(function (connection) { 15 | var query = "SELECT * FROM `" + self.table + "` WHERE id IN(" + pk.join(',') + ")"; 16 | connection.query(query, fn); 17 | }); 18 | }; 19 | 20 | Model.prototype.count = function (fn, attributes) { 21 | var self = this; 22 | this.connector.getConnection(function (connection) { 23 | var query = "SELECT COUNT(*) as cnt FROM `" + self.table + "` WHERE 1"; 24 | if (attributes) { 25 | var toQuery = []; 26 | for (var key in attributes) { 27 | toQuery.push(key + " = :" + key); 28 | } 29 | query += ' AND ' + toQuery.join(' AND '); 30 | } else { 31 | attributes = {}; 32 | } 33 | connection.query(query, attributes, function (err, result) { 34 | fn(err, err ? null : result[0]['cnt']); 35 | }); 36 | }); 37 | }; 38 | 39 | Model.prototype.get = function (fn, attributes, offset, limit) { 40 | var self = this; 41 | this.connector.getConnection(function (connection) { 42 | var where = ''; 43 | if (attributes) { 44 | var toQuery = []; 45 | for (var key in attributes) { 46 | toQuery.push(key + " = :" + key); 47 | } 48 | if (toQuery.length > 0) { 49 | where += ' AND ' + toQuery.join(' AND '); 50 | } 51 | } else { 52 | attributes = {}; 53 | } 54 | if (!offset && !limit) { 55 | connection.query("SELECT * FROM `" + self.table + "` WHERE 1 " + where, attributes, fn); 56 | } else { 57 | connection.query("SELECT * FROM `" + self.table + "` WHERE 1 " + where + " LIMIT " + offset + "," + limit, attributes, fn); 58 | } 59 | }); 60 | }; 61 | 62 | function Channel() {} 63 | Channel.prototype = new Model('ns_channel'); 64 | Channel.prototype._get = Channel.prototype.get; 65 | Channel.prototype.get = function (fn, offset, limit) { 66 | this._get(function (err, items) { 67 | if (!err) { 68 | for (var i in items) { 69 | if (items[i].properties) { 70 | items[i].properties = JSON.parse(items[i].properties); 71 | } 72 | } 73 | } 74 | fn(err, items); 75 | }, offset, limit); 76 | }; 77 | 78 | function Subscriber() {} 79 | Subscriber.prototype = new Model('ns_subscriber'); 80 | 81 | function ChannelSubscriber() {} 82 | ChannelSubscriber.prototype = new Model('ns_channel_subscriber'); 83 | 84 | var connector = { 85 | 86 | _options : null, 87 | _connectionOptions : null, 88 | _connection : null, 89 | _parseDsn : function (dsn) { 90 | this._connectionOptions['host'] = dsn.split(';')[0].split('=')[1]; 91 | this._connectionOptions['database'] = dsn.split(';')[1].split('=')[1]; 92 | }, 93 | 94 | _createConnection : function (fn) { 95 | if (!this._connectionOptions) { 96 | this._connectionOptions = {}; 97 | if (this._options['dsn']) { 98 | this._parseDsn(this._options['dsn']); 99 | } 100 | this._connectionOptions['user'] = this._options['username']; 101 | this._connectionOptions['password'] = this._options['password']; 102 | } 103 | var self = this; 104 | this._connection = mysql.createConnection(this._connectionOptions); 105 | this._connection.config.queryFormat = function (query, values) { 106 | if (!values) return query; 107 | return query.replace(/\:(\w+)/g, function (txt, key) { 108 | if (values.hasOwnProperty(key)) { 109 | return this.escape(values[key]); 110 | } 111 | return txt; 112 | }.bind(this)); 113 | }; 114 | this._connection.connect(function (err) { 115 | if (err) { 116 | console.log('error when connecting to db:', err); 117 | } else { 118 | if (fn) { 119 | fn(self._connection); 120 | } 121 | self._connection.on('error', function(err) { 122 | console.log('db error', err); 123 | if(err.code === 'PROTOCOL_CONNECTION_LOST') { // Connection to the MySQL server is usually 124 | self._createConnection(); // lost due to either server restart, or a 125 | } else { // connnection idle timeout (the wait_timeout 126 | throw err; // server variable configures this) 127 | } 128 | }); 129 | } 130 | }); 131 | }, 132 | 133 | init : function (options) { 134 | this._options = options; 135 | this.channel = new Channel(); 136 | this.channel.setConnector(this); 137 | this.subscriber = new Subscriber(); 138 | this.subscriber.setConnector(this); 139 | this.channelSubscriber = new ChannelSubscriber(); 140 | this.channelSubscriber.setConnector(this); 141 | return true; 142 | }, 143 | 144 | getConnection : function (fn) { 145 | if (this._connection) { 146 | fn(this._connection); 147 | } else { 148 | this._createConnection(fn); 149 | } 150 | }, 151 | 152 | channel : null, 153 | subscriber : null, 154 | channelSubscriber : null 155 | }; 156 | module.exports = connector; -------------------------------------------------------------------------------- /lib/js/server/components/event.manager.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var componentManager = require( __dirname + '/component.manager.js'); 3 | 4 | var events = { 5 | client : {}, 6 | server : {} 7 | }; 8 | 9 | var manager = { 10 | 11 | client : { 12 | 13 | /** 14 | * 15 | * @returns {{}} 16 | */ 17 | getEvents : function () { 18 | return events.client; 19 | }, 20 | 21 | /** 22 | * 23 | * @param socket 24 | */ 25 | bind : function (socket) { 26 | // add event listeners to socket 27 | var ev = this.getEvents(); 28 | for (var name in ev) { 29 | socket.on(name, ev[name].handler); 30 | } 31 | } 32 | }, 33 | 34 | server : { 35 | 36 | /** 37 | * 38 | * @returns {{}} 39 | */ 40 | getEvents : function () { 41 | return events.server; 42 | }, 43 | 44 | /** 45 | * 46 | * @param socket 47 | */ 48 | bind : function (socket) { 49 | // add event listeners to socket 50 | var ev = this.getEvents(); 51 | for (var name in ev) { 52 | socket.on(name, ev[name].handler); 53 | } 54 | } 55 | } 56 | }; 57 | 58 | var setEvents = function (namespace) { 59 | var files = fs.readdirSync(__dirname + '/../events/' + namespace); 60 | for (var i in files) { 61 | var ev = require('./../events/' + namespace + "/" + files[i]); 62 | if (ev) { 63 | ev.componentManager = componentManager; 64 | ev.init(); 65 | events[namespace][ev.name] = ev; 66 | } 67 | } 68 | }; 69 | setEvents('server'); 70 | setEvents('client'); 71 | 72 | module.exports = manager; -------------------------------------------------------------------------------- /lib/js/server/components/socket.pull.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | _sidPull : {}, 3 | _uidPull : {}, 4 | _volatileSidPull : {}, 5 | _volatileUidPull : {}, 6 | socketNumber : 0, 7 | add : function (socket) { 8 | this.socketNumber++; 9 | var sid = socket.handshake.sid; 10 | var uid = socket.handshake.uid; 11 | if (this._volatileSidPull[sid] && !uid) { 12 | uid = this._volatileSidPull[sid]; 13 | } 14 | if (!this._sidPull[sid]) { 15 | this._sidPull[sid] = {}; 16 | } 17 | this._sidPull[sid][socket.id] = socket; 18 | if (uid) { 19 | this.createSidUidLink(sid, uid); 20 | } 21 | var self = this; 22 | socket.on('disconnect', function () { 23 | self.socketNumber--; 24 | if (self._sidPull[sid]) { 25 | if (uid && self._uidPull[uid] && self._uidPull[uid][sid]) { 26 | // delete 27 | delete self._uidPull[uid][sid]; 28 | } 29 | // delete socket link 30 | delete self._sidPull[sid][this.id]; 31 | } 32 | }); 33 | }, 34 | createSidUidLink : function (sid, uid) { 35 | if (this._sidPull[sid]) { 36 | if (!this._uidPull[uid]) { 37 | this._uidPull[uid] = {}; 38 | } 39 | this._uidPull[uid][sid] = sid; 40 | for (var socketId in this._sidPull[sid]) { 41 | this._sidPull[sid][socketId].handshake.uid = uid; 42 | } 43 | } 44 | this._volatileSidPull[sid] = uid; 45 | if (!this._volatileUidPull[uid]) { 46 | this._volatileUidPull[uid] = {}; 47 | } 48 | this._volatileUidPull[uid][sid] = sid; 49 | }, 50 | 51 | destroySidUidLink : function (sid, uid) { 52 | if (this._uidPull[uid][sid]) { 53 | delete this._uidPull[uid][sid]; 54 | } 55 | if (this._volatileUidPull[uid][sid]) { 56 | delete this._volatileUidPull[uid][sid]; 57 | } 58 | if (this._volatileSidPull[sid]) { 59 | delete this._volatileSidPull[sid]; 60 | } 61 | }, 62 | 63 | getBySid : function (sid) { 64 | return this._sidPull[sid]; 65 | }, 66 | getByUid : function (uid) { 67 | var sids = this._uidPull[uid]; 68 | if (sids) { 69 | var sockets = {}; 70 | for(var sid in sids) { 71 | var temp = this.getBySid(sid); 72 | for(var socketId in temp) { 73 | sockets[socketId] = temp[socketId]; 74 | } 75 | } 76 | return sockets; 77 | } 78 | return null; 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /lib/js/server/events/client/channel.join.js: -------------------------------------------------------------------------------- 1 | var joinClient = { 2 | 3 | componentManager : null, 4 | 5 | name : 'channel_join', 6 | 7 | init : function () {}, 8 | 9 | handler : function (id, fn) { 10 | switch (typeof id) { 11 | 12 | case 'number': 13 | case 'string': 14 | joinToChannel(this, id, fn); 15 | return; 16 | break; 17 | } 18 | fn(false, 'Invalid channel id, valid id types [string,number]'); 19 | } 20 | }; 21 | function joinToChannel(socket, id, fn) { 22 | var channel = joinClient.componentManager.get('channel').channel.getByName(id); 23 | if (channel) { 24 | console.log(socket.handshake); 25 | channel.clientJoin(socket, fn); 26 | } else { 27 | fn({ 28 | errorMessage : 'Channel not found' 29 | }, true); 30 | } 31 | } 32 | 33 | module.exports = joinClient; -------------------------------------------------------------------------------- /lib/js/server/events/client/channel.leave.js: -------------------------------------------------------------------------------- 1 | var joinClient = { 2 | 3 | componentManager : null, 4 | 5 | name : 'channel_leave', 6 | 7 | init : function () {}, 8 | 9 | handler : function (id, fn) { 10 | console.log('Tying leave client from channel ' + id); 11 | if (typeof id == 'string' && id.length > 0) { 12 | var channel = joinClient.componentManager.get('channel').channel.getByName(id); 13 | if (channel) { 14 | console.log('Leave from channel ' + id); 15 | channel.clientLeave(this, function (success) { 16 | if (fn) { 17 | fn(success); 18 | } 19 | }); 20 | } 21 | } 22 | } 23 | }; 24 | function joinToChannel(socket, id, fn) { 25 | var channel = joinClient.componentManager.get('channel').channel.getByName(id); 26 | if (channel) { 27 | console.log(socket.handshake); 28 | channel.clientJoin(socket, fn); 29 | } else { 30 | fn({ 31 | errorMessage : 'Channel not found' 32 | }, true); 33 | } 34 | } 35 | 36 | module.exports = joinClient; -------------------------------------------------------------------------------- /lib/js/server/events/client/event.emit.js: -------------------------------------------------------------------------------- 1 | var public_data = { 2 | 3 | componentManager : null, 4 | 5 | name : 'event:emit', 6 | 7 | init : function () {}, 8 | 9 | handler : function (packet) { 10 | if (packet['event'] && packet['scope'] && packet['scope']['type']) { 11 | var client = public_data.componentManager.get('io').of('/client'); 12 | if (packet['scope']['type'] == 'global') { 13 | if (packet['broadcast'] === true) { 14 | // global event -> for all clients excluding sender 15 | client.except(this.id).emit( 16 | 'global:' + packet['event'], 17 | packet['data'] || {} 18 | ); 19 | } else { 20 | // global event -> for all clients including sender 21 | client.emit( 22 | 'global:' + packet['event'], 23 | packet['data'] || {} 24 | ); 25 | } 26 | } else if (packet['scope']['type'] == 'room' && packet['scope']['id']) { 27 | // room event -> for sockets in concrete room 28 | client.in('room:' + packet['scope']['id']).emit( 29 | 'room:' + packet['scope']['id'] + ':' + packet['event'], 30 | packet['data'] || {}, 31 | true 32 | ); 33 | } else if (packet['scope']['type'] == 'channel') { 34 | // todo implement channel event 35 | } 36 | } 37 | } 38 | }; 39 | 40 | module.exports = public_data; -------------------------------------------------------------------------------- /lib/js/server/events/client/public.data.js: -------------------------------------------------------------------------------- 1 | var public_data = { 2 | 3 | componentManager : null, 4 | 5 | name : 'public_data', 6 | 7 | init : function () {}, 8 | 9 | handler : function (key, fn) { 10 | fn( 11 | public_data.componentManager.get('publicData').get(key) 12 | ); 13 | } 14 | }; 15 | 16 | module.exports = public_data; -------------------------------------------------------------------------------- /lib/js/server/events/client/room.join.js: -------------------------------------------------------------------------------- 1 | var joinClient = { 2 | 3 | componentManager : null, 4 | 5 | name : 'room_join', 6 | 7 | init : function () {}, 8 | 9 | handler : function (id, fn) { 10 | console.log('Tying connect socket to room'); 11 | switch (typeof id) { 12 | 13 | case 'number': 14 | case 'string': 15 | console.log('Room ' + id); 16 | joinToRoom(this, id, fn); 17 | return; 18 | break; 19 | 20 | case 'object': 21 | var isJoined = {}; 22 | var numberOfRoomClients = {}; 23 | var numberOfSuccess = 0; 24 | for (var i in id) { 25 | if (id[i]) { 26 | var type = typeof id[i]; 27 | if (type == 'string' || type == 'number') { 28 | console.log('Room ' + id[i]); 29 | var room = makeRoomName(id[i]); 30 | var len = joinClient.componentManager.get('io').of('/client').clients(room).length; 31 | joinClient.componentManager.get('io').of('/client').in(room).emit(room + ':room:system:update.members_count', len + 1); 32 | this.join(room); 33 | console.log('Socket connected to ' + room); 34 | isJoined[id[i]] = true; 35 | numberOfRoomClients[id[i]] = len + 1; 36 | numberOfSuccess++; 37 | } 38 | } 39 | } 40 | if (numberOfSuccess) { 41 | fn(isJoined, numberOfRoomClients); 42 | return; 43 | } 44 | fn(false, 'Invalid or empty channel id (valid id types [string,number,array,object])'); 45 | return; 46 | break; 47 | } 48 | fn(false, 'Invalid channel id, valid id types [string,number,array,object]'); 49 | } 50 | }; 51 | 52 | function makeRoomName(id) { 53 | return 'room:' + id; 54 | } 55 | 56 | function joinToRoom(socket, roomId, fn) { 57 | var room = makeRoomName(roomId); 58 | var roomMembersCount = joinClient.componentManager.get('io').of('/client').clients(room).length; 59 | joinClient.componentManager.get('io').of('/client').in(room).emit(room + ':system:room_members_count', roomMembersCount + 1); 60 | socket.join(room); 61 | console.log('Socket connected'); 62 | fn(true, roomMembersCount + 1); 63 | } 64 | 65 | module.exports = joinClient; -------------------------------------------------------------------------------- /lib/js/server/events/client/room.leave.js: -------------------------------------------------------------------------------- 1 | var leaveClient = { 2 | 3 | componentManager : null, 4 | 5 | name : 'room_leave', 6 | 7 | init : function () {}, 8 | 9 | handler : function (id, fn) { 10 | console.log('Tying leave socket from room ' + id); 11 | if (typeof id == 'string') { 12 | this.leave('room:' + id); 13 | } 14 | if (fn) { 15 | fn(true); 16 | } 17 | } 18 | }; 19 | 20 | module.exports = leaveClient; -------------------------------------------------------------------------------- /lib/js/server/events/server/auth.js: -------------------------------------------------------------------------------- 1 | var auth = { 2 | 3 | componentManager : null, 4 | 5 | name : 'auth', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['uid']) { 11 | this.handshake.session.user.isAuthenticated = true; 12 | this.handshake.session.user.id = frame.meta.data['uid']; 13 | this.handshake.uid = frame.meta.data['uid']; 14 | this.handshake.writeSession(); 15 | auth.componentManager.get('sp').createSidUidLink(this.handshake.sid, this.handshake.uid); 16 | } 17 | } 18 | }; 19 | 20 | module.exports = auth; -------------------------------------------------------------------------------- /lib/js/server/events/server/channel_event.js: -------------------------------------------------------------------------------- 1 | var event = { 2 | 3 | componentManager : null, 4 | 5 | name : 'channel_event', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (event.actions[frame.meta.data.action]) { 11 | event.actions[frame.meta.data.action](frame); 12 | } 13 | }, 14 | 15 | actions : { 16 | 17 | getChannelManager : function () { 18 | return event.componentManager.get('channel').channel; 19 | }, 20 | 21 | getSubscriberManager : function () { 22 | return event.componentManager.get('channel').subscriber; 23 | }, 24 | 25 | 'save.YiiNodeSocket\\Models\\Channel' : function (frame) { 26 | var channel = this.getChannelManager().create(frame.data); 27 | }, 28 | 29 | 'delete.YiiNodeSocket\\Models\\Channel' : function (frame) { 30 | this.getChannelManager().delete(frame); 31 | }, 32 | 33 | 'save.YiiNodeSocket\\Models\\Subscriber' : function (frame) { 34 | event.componentManager.get('channel').subscriber.create(frame.data); 35 | }, 36 | 37 | 'delete.YiiNodeSocket\\Models\\Subscriber' : function (frame) { 38 | this.getSubscriberManager().delete(frame); 39 | }, 40 | 41 | 'save.YiiNodeSocket\\Models\\SubscriberChannel' : function (frame) { 42 | var channel = this.getChannelManager().get(frame.data.channel_id); 43 | var subscriber = this.getSubscriberManager().get(frame.data.subscriber_id); 44 | if (channel && subscriber) { 45 | channel.subscribe(subscriber, frame.data, false); 46 | } 47 | }, 48 | 49 | 'delete.YiiNodeSocket\\Models\\SubscriberChannel' : function (frame) { 50 | var channel = this.getChannelManager().get(frame.data.channel_id); 51 | var subscriber = this.getSubscriberManager().get(frame.data.subscriber_id); 52 | if (channel && subscriber) { 53 | channel.unSubscribe(subscriber); 54 | } 55 | } 56 | } 57 | }; 58 | 59 | module.exports = event; -------------------------------------------------------------------------------- /lib/js/server/events/server/event.js: -------------------------------------------------------------------------------- 1 | var event = { 2 | 3 | componentManager : null, 4 | 5 | name : 'event', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['room']) { 11 | event 12 | .componentManager 13 | .get('io') 14 | .of('/client') 15 | .in('room:' + frame.meta.data['room']) 16 | .emit('room:' + frame.meta.data['room'] + ':' + frame.meta.data.event, frame.data); 17 | } else if (frame.meta.data['channel']) { 18 | var channel = event 19 | .componentManager 20 | .get('channel') 21 | .channel 22 | .getByName(frame.meta.data['channel']); 23 | if (channel) { 24 | channel.emit(frame.meta.data.event, frame.data); 25 | } else { 26 | console.log('ERROR: channel ' + frame.meta.data['channel'] + ' not found'); 27 | } 28 | } else { 29 | event.componentManager.get('io').of('/client').emit('global:' + frame.meta.data.event, frame.data); 30 | } 31 | } 32 | }; 33 | 34 | module.exports = event; -------------------------------------------------------------------------------- /lib/js/server/events/server/invoke.js: -------------------------------------------------------------------------------- 1 | var invoke = { 2 | 3 | componentManager : null, 4 | 5 | name : 'invoke', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['room']) { 11 | invoke 12 | .componentManager 13 | .get('io') 14 | .of('/client') 15 | .in('room:' + frame.meta.data['room']) 16 | .emit('system:invoke', frame.data); 17 | } else if (frame.meta.data['channel']) { 18 | // todo need release channel event handling 19 | } else { 20 | invoke.componentManager.get('io').of('/client').emit('system:invoke', frame.data); 21 | } 22 | } 23 | }; 24 | 25 | module.exports = invoke; -------------------------------------------------------------------------------- /lib/js/server/events/server/jquery.js: -------------------------------------------------------------------------------- 1 | var invoke = { 2 | 3 | componentManager : null, 4 | 5 | name : 'jquery', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['room']) { 11 | invoke 12 | .componentManager 13 | .get('io') 14 | .of('/client') 15 | .in('room:' + frame.meta.data['room']) 16 | .emit('system:jquery', frame.data); 17 | } else if (frame.meta.data['channel']) { 18 | var channel = event 19 | .componentManager 20 | .get('channel') 21 | .channel 22 | .getByName(frame.meta.data['channel']); 23 | if (channel) { 24 | channel.emit('system:jquery', frame.data); 25 | } else { 26 | console.log('ERROR: channel ' + frame.meta.data['channel'] + ' not found'); 27 | } 28 | } else { 29 | invoke.componentManager.get('io').of('/client').emit('system:jquery', frame.data); 30 | } 31 | } 32 | }; 33 | 34 | module.exports = invoke; -------------------------------------------------------------------------------- /lib/js/server/events/server/logout.js: -------------------------------------------------------------------------------- 1 | var logout = { 2 | 3 | componentManager : null, 4 | 5 | name : 'logout', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['uid']) { 11 | if (this.handshake.session.user.isAuthenticated) { 12 | this.handshake.session.user.isAuthenticated = false; 13 | var uid = this.handshake.session.user.id; 14 | this.handshake.session.user.id = null; 15 | this.handshake.uid = null; 16 | this.handshake.writeSession(); 17 | logout.componentManager.get('sp').destroySidUidLink(this.handshake.sid, uid); 18 | } 19 | } 20 | } 21 | }; 22 | 23 | module.exports = logout; -------------------------------------------------------------------------------- /lib/js/server/events/server/multiple.frame.js: -------------------------------------------------------------------------------- 1 | var multipleFrame = { 2 | 3 | componentManager : null, 4 | 5 | name : 'multi_frame', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | var handlers = multipleFrame.componentManager.get('eventManager').server.getEvents(); 11 | for (var event in frame.meta.data) { 12 | if (handlers[event]) { 13 | for (var id in frame.meta.data[event]) { 14 | handlers[event].handler.call(this, { 15 | meta : frame.meta.data[event][id], 16 | data : frame.data[event][id] 17 | }); 18 | } 19 | } 20 | } 21 | } 22 | }; 23 | 24 | module.exports = multipleFrame; -------------------------------------------------------------------------------- /lib/js/server/events/server/public.data.js: -------------------------------------------------------------------------------- 1 | var public_data = { 2 | 3 | componentManager : null, 4 | 5 | name : 'public_data', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | public_data.componentManager.get('publicData').set(frame.meta.data.key, frame.data, frame.meta.data.lifetime); 11 | } 12 | }; 13 | 14 | module.exports = public_data; -------------------------------------------------------------------------------- /lib/js/server/events/server/userEvent.js: -------------------------------------------------------------------------------- 1 | var userEvent = { 2 | 3 | componentManager : null, 4 | 5 | name : 'userEvent', 6 | 7 | init : function () {}, 8 | 9 | handler : function (frame) { 10 | if (frame.meta.data['uid']) { 11 | var sockets = []; 12 | var sp = userEvent.componentManager.get('sp'); 13 | var sessionSockets = null; 14 | for (var i in frame.meta.data['uid']) { 15 | if (sessionSockets = sp.getByUid(frame.meta.data['uid'][i])) { 16 | for (var id in sessionSockets) { 17 | sockets.push(sessionSockets[id]); 18 | } 19 | } 20 | } 21 | if (sockets.length > 0) { 22 | var sendEvent = function (socket) { 23 | if (frame.meta.data['room']) { 24 | socket.emit('room:' + frame.meta.data['room'] + ':' + frame.meta.data.event, frame.data); 25 | } else if (frame.meta.data['channel']) { 26 | socket.emit('channel:' + frame.meta.data['channel'] + ':' + frame.meta.data.event, frame.data); 27 | } else { 28 | socket.emit('global:' + frame.meta.data.event, frame.data); 29 | } 30 | }; 31 | for (var i in sockets) { 32 | sendEvent(sockets[i]); 33 | } 34 | } 35 | } 36 | } 37 | }; 38 | 39 | module.exports = userEvent; -------------------------------------------------------------------------------- /lib/js/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "yii-node-socket", 3 | "description" : "Yii component for sending events from php and catch it on javascript side", 4 | "version" : "0.1.0", 5 | "dependencies" : { 6 | "express" : "3.x", 7 | "socket.io" : "0.9.x", 8 | "cookie" : ">=0.1.x", 9 | "mongoose" : ">=3.6.x", 10 | "mysql" : ">=2.x" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/js/server/server.config.js.php: -------------------------------------------------------------------------------- 1 | 7 | module.exports = { 8 | host : 'host; ?>', 9 | port : parseInt('port; ?>'), 10 | origin : 'getOrigin(); ?>', 11 | allowedServers : getAllowedServersAddresses()); ?>, 12 | dbOptions : getDb()->getConnectionOptions()); ?>, 13 | checkClientOrigin : checkClientOrigin; ?>, 14 | sessionVarName : 'sessionVarName; ?>', 15 | socketLogFile : 'socketLogFile; ?>' 16 | }; 17 | 18 | _nodeSocketDirectory = $dir . DIRECTORY_SEPARATOR; 18 | } 19 | 20 | public function autoload($className, $classMapOnly=false) { 21 | if (strpos($className, 'YiiNodeSocket\\') === 0) { 22 | $class = $this->_nodeSocketDirectory; 23 | $chunks = explode('\\', $className); 24 | array_shift($chunks); 25 | $cl = array_pop($chunks); 26 | $class .= strtolower(join('/', $chunks)) . '/' . $cl . '.php'; 27 | if (file_exists($class)) { 28 | include $class; 29 | return true; 30 | } 31 | return false; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /lib/php/NodeSocket.php: -------------------------------------------------------------------------------- 1 | 'test.com' 23 | * // or 24 | * 'host' => '84.25.159.52' 25 | * 26 | * @var string 27 | */ 28 | public $host = '0.0.0.0'; 29 | 30 | 31 | 32 | /** 33 | * If your session var name is SID or other change this value to it 34 | * 35 | * @var string 36 | */ 37 | public $sessionVarName = 'PHPSESSID'; 38 | 39 | /** 40 | * @var int by default is once month 41 | */ 42 | public $cookieLifeTime = 2592000; 43 | 44 | /** 45 | * Port in integer type only 46 | * 47 | * @var int 48 | */ 49 | public $port = 3001; 50 | 51 | /** 52 | * Can be string, every domain|ip separated by a comma 53 | * or array 54 | * 55 | * @var string|array 56 | */ 57 | public $origin; 58 | 59 | /** 60 | * List of allowed servers 61 | * 62 | * Who can send server frames 63 | * 64 | * If is string, ip addresses should be separated by a comma 65 | * 66 | * @var string|array 67 | */ 68 | public $allowedServerAddresses; 69 | 70 | /** 71 | * Default is runtime/socket-transport.server.log 72 | * 73 | * @var string 74 | */ 75 | public $socketLogFile; 76 | 77 | /** 78 | * If set to false, any client can connect to websocket server 79 | * 80 | * @var bool 81 | */ 82 | public $checkClientOrigin = true; 83 | 84 | /** 85 | * @var string 86 | */ 87 | public $pidFile = 'socket-transport.pid'; 88 | 89 | /** 90 | * @var int timeout for handshaking in miliseconds 91 | */ 92 | public $handshakeTimeout = 2000; 93 | 94 | /** 95 | * @var array 96 | */ 97 | public $dbConfiguration = array('driver' => 'dummy'); 98 | 99 | /** 100 | * @var string 101 | */ 102 | protected $_assetUrl; 103 | 104 | /** 105 | * @var \YiiNodeSocket\Frames\FrameFactory 106 | */ 107 | protected $_frameFactory; 108 | 109 | /** 110 | * @var \YiiNodeSocket\Components\Db 111 | */ 112 | protected $_db; 113 | 114 | /** 115 | * @var \ElephantIO\Client 116 | */ 117 | protected $_client; 118 | 119 | /** 120 | * SSL keys 121 | * 122 | * @var string 123 | */ 124 | public $filePem; 125 | public $fileCrt; 126 | 127 | /** 128 | * Elephant.io URL http or https 129 | * 130 | * @var string 131 | */ 132 | public $protocol; 133 | 134 | 135 | public function init() { 136 | parent::init(); 137 | 138 | // spl_autoload_unregister(array('YiiBase','autoload')); 139 | require_once 'Autoload.php'; 140 | \YiiNodeSocket\Autoload::register(__DIR__); 141 | // spl_autoload_register(array('YiiBase','autoload')); 142 | if (function_exists('__autoload')) { 143 | // Be polite and ensure that userland autoload gets retained 144 | spl_autoload_register('__autoload'); 145 | } 146 | $this->_frameFactory = new \YiiNodeSocket\Frames\FrameFactory($this); 147 | $this->_db = new \YiiNodeSocket\Components\Db($this); 148 | foreach ($this->dbConfiguration as $k => $v) { 149 | $this->_db->$k = $v; 150 | } 151 | } 152 | 153 | /** 154 | * @return \YiiNodeSocket\Frames\FrameFactory 155 | */ 156 | public function getFrameFactory() { 157 | return $this->_frameFactory; 158 | } 159 | 160 | /** 161 | * @return \YiiNodeSocket\Components\Db 162 | */ 163 | public function getDb() { 164 | return $this->_db; 165 | } 166 | 167 | /** 168 | * @return \YiiNodeSocket\Components\Db 169 | */ 170 | public function getClient() { 171 | return $this->_client; 172 | } 173 | 174 | /** 175 | * @return bool 176 | */ 177 | public function registerClientScripts() { 178 | 179 | if ($this->_assetUrl) { 180 | return true; 181 | } 182 | 183 | $assets = NodeSocketAssets::register(\Yii::$app->getView()); 184 | $this->_assetUrl = $assets->publish('@nodeWeb'); 185 | if ($this->_assetUrl) { 186 | return true; 187 | } 188 | return false; 189 | } 190 | 191 | /** 192 | * @return string 193 | */ 194 | public function getOrigin() { 195 | // $origin = $this->host . ':*'; 196 | 197 | $origin = ''; 198 | if ($this->origin) { 199 | $o = array(); 200 | if (is_string($this->origin)) { 201 | $o = explode(',', $this->origin); 202 | } 203 | $o = array_map('trim', $o); 204 | if (in_array($origin, $o)) { 205 | unset($o[array_search($origin, $o)]); 206 | } 207 | if (!empty($o)) { 208 | $origin .= ' ' . implode(' ', $o); 209 | } 210 | } 211 | if (!$origin) { 212 | $origin = $this->host . ':*'; 213 | } 214 | return $origin; 215 | } 216 | 217 | /** 218 | * @return array 219 | */ 220 | public function getAllowedServersAddresses() { 221 | $allow = array(); 222 | $serverIp = gethostbyname($this->host); 223 | $allow[] = $serverIp; 224 | if ($this->allowedServerAddresses && !empty($this->allowedServerAddresses)) { 225 | if (is_string($this->allowedServerAddresses)) { 226 | $allow = array_merge($allow, explode(',', $this->allowedServerAddresses)); 227 | } else if (is_array($this->allowedServerAddresses)) { 228 | $allow = array_merge($allow, $this->allowedServerAddresses); 229 | } 230 | } 231 | return array_unique($allow); 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /lib/php/NodeSocketCommand.php: -------------------------------------------------------------------------------- 1 | stdout( 37 | "node-socket usage: \n\n" 38 | ."./yii node-socket # show help\n" 39 | ."./yii node-socket/start # start server\n" 40 | ."./yii node-socket/stop # stop server\n" 41 | ."./yii node-socket/restart # restart server\n" 42 | ."./yii node-socket/get-pid # show pid of nodejs process\n\n" 43 | ); 44 | } 45 | 46 | public function actionStart() { 47 | if (!$this->_start()) { 48 | $this->usageError("Cannot start server"); 49 | } 50 | exit(1); 51 | } 52 | 53 | public function actionStop() { 54 | if (!$this->_stop()) { 55 | exit(1); 56 | } 57 | } 58 | 59 | public function actionRestart() { 60 | if ($this->_stop()) { 61 | if (!$this->_start()) { 62 | $this->usageError("Cannot start server"); 63 | } 64 | exit(1); 65 | } else { 66 | $this->usageError('Cannot stop server'); 67 | } 68 | } 69 | 70 | public function actionGetPid() { 71 | echo (int)$this->getPid() . "\n"; 72 | } 73 | 74 | /** 75 | * @param $error 76 | */ 77 | protected function usageError($error) { 78 | print "ERROR: " . $error . "\n"; 79 | exit(1); 80 | } 81 | 82 | public function getHelp() { 83 | return <<getComponent(); 108 | ob_start(); 109 | include __DIR__ . '/../js/server/server.config.js.php'; 110 | $js = ob_get_clean(); 111 | return file_put_contents(__DIR__ . '/../js/server/server.config.js', $js); 112 | } 113 | 114 | protected function compileClient() { 115 | printf("Compile client\n"); 116 | $nodeSocket = $this->getComponent(); 117 | ob_start(); 118 | include __DIR__ . '/../js/client/client.template.js'; 119 | $js = ob_get_clean(); 120 | return file_put_contents(__DIR__ . '/../js/client/client.js', $js); 121 | } 122 | 123 | /** 124 | * @return string 125 | */ 126 | protected function makeCommand() { 127 | $server = implode(DIRECTORY_SEPARATOR, array( 128 | __DIR__, 129 | '..', 130 | 'js', 131 | 'server', 132 | 'server.js' 133 | )); 134 | return $this->pathToNodeJs . ' ' . $server; 135 | } 136 | 137 | /** 138 | * @return string 139 | */ 140 | protected function getLogFile() { 141 | $logFile = $this->getComponent()->socketLogFile; 142 | if ($logFile) { 143 | return $logFile; 144 | } 145 | return \Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'socket-transport.server.log'; 146 | } 147 | 148 | /** 149 | * @return bool 150 | */ 151 | protected function isInProgress() { 152 | $pid = $this->getPid(); 153 | if ($pid == 0) { 154 | return false; 155 | } 156 | return $this->getConsole()->isInProgress($pid); 157 | } 158 | 159 | /** 160 | * @return NodeSocket 161 | */ 162 | protected function getComponent() { 163 | $nodeSocket = \Yii::$app->get($this->componentName); 164 | 165 | if ($nodeSocket) { 166 | return $nodeSocket; 167 | } 168 | $this->usageError('Please provide valid socket transport component name like in config'); 169 | } 170 | 171 | /** 172 | * @return int 173 | */ 174 | protected function writePid() { 175 | printf("Update pid in file %s\n", $this->getPidFile()); 176 | return file_put_contents($this->getPidFile(), $this->getPid()); 177 | } 178 | 179 | /** 180 | * @return int|null 181 | */ 182 | protected function getPid($update = false) { 183 | if (isset($this->_pid) && !$update) { 184 | return $this->_pid; 185 | } 186 | if ($update || !isset($this->_pid)) { 187 | $this->updatePid(); 188 | } 189 | return $this->_pid; 190 | } 191 | 192 | /** 193 | * Update process pid 194 | */ 195 | protected function updatePid() { 196 | $this->_pid = 0; 197 | $pidFile = $this->getPidFile(); 198 | if (file_exists($pidFile)) { 199 | $this->_pid = (int)file_get_contents($pidFile); 200 | } 201 | } 202 | 203 | /** 204 | * @return string 205 | */ 206 | protected function getPidFile() { 207 | return \Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . $this->getComponent()->pidFile; 208 | } 209 | 210 | /** 211 | * @return bool|int 212 | */ 213 | protected function _stop() { 214 | $pid = $this->getPid(); 215 | if ($pid && $this->isInProgress()) { 216 | printf("Stopping socket server\n"); 217 | $this->getConsole()->stopServer($this->getPid()); 218 | if (!$this->isInProgress()) { 219 | printf("Server successfully stopped\n"); 220 | $this->_pid = 0; 221 | return $this->writePid(); 222 | } 223 | printf("Stopping server error\n"); 224 | return false; 225 | } 226 | printf("Server is stopped\n"); 227 | return true; 228 | } 229 | 230 | /** 231 | * @return bool|int 232 | */ 233 | protected function _start() { 234 | if ($this->getPid() && $this->isInProgress()) { 235 | printf("Server already started\n"); 236 | return true; 237 | } 238 | $this->compileServer(); 239 | $this->compileClient(); 240 | printf("Starting server\n"); 241 | $this->_pid = $this->getConsole()->startServer($this->makeCommand(), $this->getLogFile()); 242 | if ($this->_pid) { 243 | printf("Server successfully started\n"); 244 | return $this->writePid(); 245 | } 246 | return false; 247 | } 248 | 249 | 250 | /** 251 | * @return \YiiNodeSocket\Console\ConsoleInterface 252 | */ 253 | private function getConsole() { 254 | if ($this->_console) { 255 | return $this->_console; 256 | } 257 | if (strpos(PHP_OS, 'WIN') !== false) { 258 | $this->_console = new \YiiNodeSocket\Console\WinConsole(); 259 | } else { 260 | $this->_console = new \YiiNodeSocket\Console\UnixConsole(); 261 | } 262 | return $this->_console; 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /lib/php/assets/NodeSocketAssets.php: -------------------------------------------------------------------------------- 1 | 10 | * @since 1.0 11 | */ 12 | class NodeSocketAssets extends AssetBundle 13 | { 14 | 15 | public $sourcePath = '@nodeWeb'; 16 | 17 | /** 18 | * Overridden by Setting the above attribute it 19 | * Forces Yii into using the asset caching library. 20 | * 21 | public $basePath = '@webroot'; 22 | public $baseUrl = '@web'; 23 | * 24 | */ 25 | public $css = [ 26 | ]; 27 | public $js = [ 28 | ]; 29 | public $depends = [ 30 | ]; 31 | 32 | public function init() 33 | { 34 | $this->js[] = sprintf( 35 | "http://%s:%d%s", Yii::$app->nodeSocket->host, Yii::$app->nodeSocket->port, '/socket.io/socket.io.js' 36 | ); 37 | $this->js[] = 'client/client.js'; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /lib/php/behaviors/ABehavior.php: -------------------------------------------------------------------------------- 1 | hasComponent($this->componentName)) { 30 | throw new \CException('Node socket component not found with the name `' . $this->componentName . "`"); 31 | } 32 | return \Yii::app()->getComponent($this->componentName); 33 | } 34 | 35 | /** 36 | * @param ArEvent $event 37 | */ 38 | protected function triggerModelEvent(ArEvent $event) { 39 | $owner = $this->getOwner(); 40 | if ($owner->hasEvent($event->name)) { 41 | $owner->raiseEvent($event->name, $event); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /lib/php/behaviors/ArChannel.php: -------------------------------------------------------------------------------- 1 | getChannel()) { 57 | return $channel->subscribe($model->getSubscriber()); 58 | } 59 | } catch (\CException $e) {} 60 | return false; 61 | } 62 | 63 | /** 64 | * @param \CActiveRecord $model 65 | * 66 | * @return bool 67 | */ 68 | public function unSubscribe(\CActiveRecord $model) { 69 | try { 70 | if ($channel = $this->getChannel()) { 71 | return $channel->unSubscribe($model->getSubscriber()); 72 | } 73 | } catch(\CException $e) {} 74 | return false; 75 | } 76 | 77 | /** 78 | * @param string $name 79 | * 80 | * @return \YiiNodeSocket\Frames\Event|null 81 | */ 82 | public function createEvent($name) { 83 | if ($channel = $this->getChannel()) { 84 | return $channel->createEvent($name); 85 | } 86 | return null; 87 | } 88 | 89 | public function createChannel() { 90 | $this->_channel = new Channel(); 91 | $this->attributesToChannel($this->_channel); 92 | $this->_channel->name = $this->getChannelName(); 93 | 94 | $event = new ArEvent($this); 95 | $event->name = 'onChannelSave'; 96 | $event->error = !$this->_channel->save(); 97 | $this->triggerModelEvent($event); 98 | } 99 | 100 | public function updateChannel() { 101 | if ($channel = $this->getChannel()) { 102 | $this->attributesToChannel($channel); 103 | 104 | $event = new ArEvent($this); 105 | $event->name = 'onChannelSave'; 106 | $event->error = !$channel->save(); 107 | $this->triggerModelEvent($event); 108 | } 109 | } 110 | 111 | public function deleteChannel() { 112 | if ($channel = $this->getChannel()) { 113 | $event = new ArEvent($this); 114 | $event->name = 'onChannelDelete'; 115 | $event->error = !$channel->delete(); 116 | $this->triggerModelEvent($event); 117 | } 118 | } 119 | 120 | /** 121 | * @return Channel 122 | */ 123 | public function getChannel() { 124 | if (isset($this->_channel)) { 125 | return $this->_channel; 126 | } 127 | $this->_channel = Channel::model()->findByAttributes(array( 128 | 'name' => $this->getChannelName() 129 | )); 130 | return $this->_channel; 131 | } 132 | 133 | /** 134 | * @return string 135 | */ 136 | public function getAlias() { 137 | if ($this->alias) { 138 | return $this->alias; 139 | } 140 | return $this->alias = strtolower(get_class($this->getOwner())); 141 | } 142 | 143 | /** 144 | * @param \CModelEvent $event 145 | */ 146 | public function afterSave($event) { 147 | if ($this->getOwner()->getIsNewRecord() && $this->createOnSave) { 148 | $this->createChannel(); 149 | } else if (!$this->getOwner()->getIsNewRecord() && $this->updateOnSave) { 150 | $this->updateChannel(); 151 | } 152 | } 153 | 154 | /** 155 | * @param \CModelEvent $event 156 | */ 157 | public function afterDelete($event) { 158 | if ($this->deleteOnDelete) { 159 | $this->deleteChannel(); 160 | } 161 | } 162 | 163 | /** 164 | * @param Channel $channel 165 | */ 166 | protected function attributesToChannel(Channel $channel) { 167 | foreach ($this->attributes as $attribute => $value) { 168 | if ($attribute != 'name') { 169 | $channel->$attribute = $value; 170 | } 171 | } 172 | $properties = $this->getOwner()->getAttributes($this->properties); 173 | $properties['alias'] = $this->getAlias(); 174 | $channel->properties = $properties; 175 | } 176 | 177 | /** 178 | * @return string 179 | */ 180 | protected function getChannelName() { 181 | $pk = $this->getOwner()->getPrimaryKey(); 182 | if (is_array($pk)) { 183 | $pk = md5(\CJSON::encode($pk)); 184 | } 185 | return get_class($this->getOwner()) . ':' . $pk; 186 | } 187 | } -------------------------------------------------------------------------------- /lib/php/behaviors/ArChannelTrigger.php: -------------------------------------------------------------------------------- 1 | getOwner()->hasRelated($relation)) { 38 | $relation = $this->getOwner()->getActiveRelation($relation); 39 | 40 | if ($relation instanceof \CBelongsToRelation) { 41 | if (is_string($relation->foreignKey) && $relatedAttributeValue = $this->getOwner()->getAttribute($relation->foreignKey)) { 42 | try { 43 | $model = \CActiveRecord::model($relation->className); 44 | $channel = $model->getChannel(); 45 | if ($channel instanceof Channel) { 46 | $event = $channel->createEvent(); 47 | $event->setData($this->fetchModelAttributes()); 48 | return $event; 49 | } 50 | } catch (\CException $e) {} 51 | } 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | protected function triggerRelations($eventPrefix) { 58 | $multiple = $this->getNodeSocketComponent()->getFrameFactory()->createMultipleFrame(); 59 | foreach ($this->relations as $relation) { 60 | $eventFrame = $this->getRelationEvent($relation); 61 | if ($eventFrame) { 62 | $eventFrame->setEventName($this->createEventName($eventPrefix)); 63 | $multiple->addFrame($eventFrame); 64 | } 65 | } 66 | $multiple->send(); 67 | } 68 | 69 | /** 70 | * @param \CModelEvent $event 71 | */ 72 | protected function afterSave(\CModelEvent $event) { 73 | if ($this->getOwner()->getIsNewRecord()) { 74 | if ($this->triggerOnCreate) { 75 | $this->triggerRelations('onCreate'); 76 | } 77 | } else { 78 | if ($this->triggerOnUpdate) { 79 | if (!$this->compareAttributesOnUpdate || $this->isAttributesChanged()) { 80 | $this->triggerRelations('onUpdate'); 81 | } 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * @param \CModelEvent $event 88 | */ 89 | protected function afterDelete(\CModelEvent $event) { 90 | if ($this->triggerOnDelete) { 91 | $this->triggerRelations('onDelete'); 92 | } 93 | } 94 | 95 | /** 96 | * @return bool 97 | */ 98 | protected function isAttributesChanged() { 99 | $diff = array_diff($this->fetchModelAttributes(), $this->_attributes); 100 | return !empty($diff); 101 | } 102 | 103 | /** 104 | * @param string $prefix 105 | * 106 | * @return string 107 | */ 108 | protected function createEventName($prefix) { 109 | return $prefix . '.' . strtolower(get_class($this->getOwner())); 110 | } 111 | 112 | protected function afterFind(\CModelEvent $event) { 113 | $this->_attributes = $this->fetchModelAttributes(); 114 | } 115 | 116 | /** 117 | * @return array 118 | */ 119 | protected function fetchModelAttributes() { 120 | return $this->getOwner()->getAttributes($this->attributes); 121 | } 122 | } -------------------------------------------------------------------------------- /lib/php/behaviors/ArSubscriber.php: -------------------------------------------------------------------------------- 1 | _subscriber) { 48 | return $this->_subscriber; 49 | } 50 | if ($this->getOwner()->getIsNewRecord()) { 51 | return null; 52 | } 53 | return $this->_subscriber = Subscriber::model()->findByAttributes(array( 54 | 'user_id' => $this->_getUserId() 55 | )); 56 | } 57 | 58 | public function createSubscriber() { 59 | if (!$this->getSubscriber()) { 60 | $this->_subscriber = new Subscriber(); 61 | $this->_subscriber->user_id = $this->_getUserId(); 62 | $this->_subscriber->role = $this->getRole(); 63 | 64 | $event = new ArEvent($this); 65 | $event->name = 'onSubscriberSave'; 66 | $event->error = !$this->_subscriber->save(); 67 | $this->triggerModelEvent($event); 68 | } 69 | } 70 | 71 | public function updateSubscriber() { 72 | if ($subscriber = $this->getSubscriber()) { 73 | $subscriber->role = $this->getRole(); 74 | 75 | $event = new ArEvent($this); 76 | $event->name = 'onSubscriberSave'; 77 | $event->error = !$subscriber->save(); 78 | $this->triggerModelEvent($event); 79 | } 80 | } 81 | 82 | public function deleteSubscriber() { 83 | if ($subscriber = $this->getSubscriber()) { 84 | $event = new ArEvent($this); 85 | $event->name = 'onSubscriberDelete'; 86 | $event->error = !$subscriber->delete(); 87 | $this->triggerModelEvent($event); 88 | } 89 | } 90 | 91 | public function afterSave($event) { 92 | if ($this->getOwner()->getIsNewRecord()) { 93 | if ($this->createOnSave) { 94 | $this->createSubscriber(); 95 | } 96 | } else { 97 | if ($this->updateOnSave) { 98 | $this->updateSubscriber(); 99 | } 100 | } 101 | } 102 | 103 | public function afterDelete($event) { 104 | if ($this->deleteOnDelete) { 105 | $this->deleteSubscriber(); 106 | } 107 | } 108 | 109 | /** 110 | * @return mixed|null|string 111 | */ 112 | protected function getRole() { 113 | if ($this->roleAttribute && $this->getOwner()->hasAttribute($this->roleAttribute)) { 114 | return $this->getOwner()->getAttribute($this->roleAttribute); 115 | } else if ($this->role) { 116 | return $this->role; 117 | } 118 | return null; 119 | } 120 | 121 | /** 122 | * @return mixed 123 | */ 124 | private function _getUserId() { 125 | return $this->getOwner()->getPrimaryKey(); 126 | } 127 | } -------------------------------------------------------------------------------- /lib/php/components/AComponent.php: -------------------------------------------------------------------------------- 1 | _nodeSocket = $nodeSocket; 18 | } 19 | } -------------------------------------------------------------------------------- /lib/php/components/ArEvent.php: -------------------------------------------------------------------------------- 1 | _driver) { 28 | return $this->_driver; 29 | } 30 | return $this->_driver = $this->_createDriver(); 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getConnectionOptions() { 37 | return array_merge($this->getDriver()->getConnectionOptions(), array( 38 | 'driver' => $this->driver, 39 | 'config' => $this->config 40 | )); 41 | } 42 | 43 | /** 44 | * @return DriverInterface 45 | * @throws \CException 46 | */ 47 | protected function _createDriver() { 48 | if (!$this->driver) { 49 | throw new \CException('Invalid node socket db driver'); 50 | } 51 | $class = '\YiiNodeSocket\Components\Db\\' . ucfirst($this->driver) . '\\' . ucfirst($this->driver) . 'Driver'; 52 | $driver = new $class(); 53 | /** @var \YiiNodeSocket\Components\Db\DriverInterface $driver */ 54 | $driver->init($this->config); 55 | return $driver; 56 | } 57 | } -------------------------------------------------------------------------------- /lib/php/components/db/BaseDriver.php: -------------------------------------------------------------------------------- 1 | resolveModelName($model); 33 | if (method_exists($this, $method)) { 34 | return $this->$method($model); 35 | } 36 | return null; 37 | } 38 | } -------------------------------------------------------------------------------- /lib/php/components/db/DriverInterface.php: -------------------------------------------------------------------------------- 1 | \Yii::app()->db->connectionString, 22 | 'username' => \Yii::app()->db->username, 23 | 'password' => \Yii::app()->db->password, 24 | 'charset' => \Yii::app()->db->charset 25 | ); 26 | } 27 | 28 | /** 29 | * @param AModel $model 30 | * 31 | * @return boolean 32 | */ 33 | public function save(AModel $model) { 34 | $modelClassName = $this->resolveModelName($model); 35 | if ($model->getIsNewRecord()) { 36 | $newModel = new $modelClassName(); 37 | } else { 38 | $newModel = $this->_resolveModel($model)->findByPk($model->id); 39 | if (!isset($newModel)) { 40 | $newModel = new $modelClassName(); 41 | } 42 | } 43 | /** @var \CActiveRecord $model */ 44 | $newModel->setAttributes($model->getAttributes()); 45 | if ($newModel->save()) { 46 | $model->load($newModel->getAttributes()); 47 | return true; 48 | } 49 | $model->addErrors($newModel->getErrors()); 50 | return false; 51 | } 52 | 53 | /** 54 | * @param AModel $model 55 | * 56 | * @return boolean 57 | */ 58 | public function refresh(AModel $model) { 59 | if (!$model->getIsNewRecord()) { 60 | $m = $this->_resolveModel($model)->findByPk($model->id); 61 | if ($m) { 62 | $model->load($m->getAttributes()); 63 | return true; 64 | } 65 | } 66 | return false; 67 | } 68 | 69 | /** 70 | * @param AModel $model 71 | * 72 | * @return boolean 73 | */ 74 | public function delete(AModel $model) { 75 | if (!$model->getIsNewRecord()) { 76 | $m = $this->_resolveModel($model)->findByPk($model->id); 77 | if ($m) { 78 | return $m->delete(); 79 | } 80 | } 81 | return true; 82 | } 83 | 84 | /** 85 | * @param $pk 86 | * @param AModel $model 87 | * 88 | * @return AModel 89 | */ 90 | public function findByPk($pk, AModel $model) { 91 | $foundModel = $this->_resolveModel($model)->findByPk($pk); 92 | if ($foundModel) { 93 | $newInstance = $model->newInstance('update'); 94 | $newInstance->load($foundModel->getAttributes()); 95 | return $newInstance; 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * @param array $pk 102 | * @param AModel $model 103 | * 104 | * @return AModel[] 105 | */ 106 | public function findAllByPk(array $pk, AModel $model) { 107 | $foundModels = $this->_resolveModel($model)->findAllByPk($pk); 108 | $result = array(); 109 | foreach ($foundModels as $m) { 110 | $newInstance = $model->newInstance('update'); 111 | $newInstance->load($m->getAttributes()); 112 | $result[] = $newInstance; 113 | } 114 | return $result; 115 | } 116 | 117 | /** 118 | * @param array $attributes 119 | * @param AModel $model 120 | * 121 | * @return AModel 122 | */ 123 | public function findByAttributes(array $attributes, AModel $model) { 124 | $arModel = $this->_resolveModel($model)->findByAttributes($attributes); 125 | if ($arModel) { 126 | $newModel = $model->newInstance('update'); 127 | $newModel->load($arModel->getAttributes()); 128 | return $newModel; 129 | } 130 | return null; 131 | } 132 | 133 | /** 134 | * @param array $attributes 135 | * @param AModel $model 136 | * 137 | * @return AModel[] 138 | */ 139 | public function findAllByAttributes(array $attributes, AModel $model) { 140 | $models = $this->_resolveModel($model)->findAllByAttributes($attributes); 141 | $foundedModels = array(); 142 | foreach ($models as $m) { 143 | $newInstance = $model->newInstance('update'); 144 | $newInstance->load($m->getAttributes()); 145 | $foundedModels[] = $newInstance; 146 | } 147 | return $foundedModels; 148 | } 149 | 150 | /** 151 | * @param AModel $model 152 | * 153 | * @return string 154 | */ 155 | protected function resolveModelName(AModel $model) { 156 | return '\YiiNodeSocket\Components\Db\Mysql\Models\\' . 'Ns' . parent::resolveModelName($model); 157 | } 158 | 159 | /** 160 | * @param AModel $nsModel 161 | * 162 | * @return \CActiveRecord 163 | */ 164 | private function _resolveModel(AModel $nsModel) { 165 | return call_user_func(array($this->resolveModelName($nsModel), 'model')); 166 | } 167 | } -------------------------------------------------------------------------------- /lib/php/components/db/mysql/m131126_114536_node_socket_migration.php: -------------------------------------------------------------------------------- 1 | createTable('ns_channel', array( 7 | 'id' => 'pk', 8 | 'name' => 'string not null', 9 | 'properties' => 'text', 10 | 'is_authentication_required' => 'boolean not null', 11 | 'allowed_roles' => 'string', 12 | 'subscriber_source' => 'boolean not null', 13 | 'event_source' => 'boolean not null', 14 | 'create_date' => 'timestamp' 15 | ), 'ENGINE=InnoDb default charset=utf8 collate utf8_general_ci'); 16 | 17 | $this->createIndex('ns_channel_unique_name', 'ns_channel', 'name', true); 18 | $this->createIndex('ns_channel_name', 'ns_channel', 'name'); 19 | 20 | $this->createTable('ns_subscriber', array( 21 | 'id' => 'pk', 22 | 'role' => 'string', 23 | 'user_id' => 'integer', 24 | 'sid' => 'string', 25 | 'sid_expiration' => 'datetime', 26 | 'create_date' => 'timestamp' 27 | ), 'ENGINE=InnoDb default charset=utf8 collate utf8_general_ci'); 28 | 29 | $this->createTable('ns_channel_subscriber', array( 30 | 'id' => 'pk', 31 | 'subscriber_id' => 'integer not null', 32 | 'channel_id' => 'integer not null', 33 | 'can_send_event_from_php' => 'boolean not null', 34 | 'can_send_event_from_js' => 'boolean not null', 35 | 'create_date' => 'timestamp' 36 | ), 'ENGINE=InnoDb default charset=utf8 collate utf8_general_ci'); 37 | 38 | $this->addForeignKey('ns_channel_subscriber_subscriber_id_fk', 'ns_channel_subscriber', 'subscriber_id', 'ns_subscriber', 'id', 'CASCADE', 'CASCADE'); 39 | $this->addForeignKey('ns_channel_subscriber_channel_id_fk', 'ns_channel_subscriber', 'channel_id', 'ns_channel', 'id', 'CASCADE', 'CASCADE'); 40 | } 41 | 42 | public function down() { 43 | $this->dropForeignKey('ns_channel_subscriber_subscriber_id_fk', 'ns_channel_subscriber'); 44 | $this->dropForeignKey('ns_channel_subscriber_channel_id_fk', 'ns_channel_subscriber'); 45 | 46 | $this->dropTable('ns_channel'); 47 | $this->dropTable('ns_subscriber'); 48 | $this->dropTable('ns_channel_subscriber'); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/php/components/db/mysql/models/NsChannel.php: -------------------------------------------------------------------------------- 1 | true), 52 | array('properties', 'length', 'max' => 65000, 'allowEmpty' => true), 53 | array('name, allowed_roles', 'length', 'max' => 255), 54 | // The following rule is used by search(). 55 | // Please remove those attributes that should not be searched. 56 | array('id, name, properties, is_authentication_required, allowed_roles, subscriber_source, event_source, create_date', 'safe', 'on' => 'search'), 57 | ); 58 | } 59 | 60 | /** 61 | * @return array relational rules. 62 | */ 63 | public function relations() { 64 | // NOTE: you may need to adjust the relation name and the related 65 | // class name for the relations automatically generated below. 66 | return array( 67 | 'nsChannelSubscribers' => array(self::HAS_MANY, 'NsChannelSubscriber', 'channel_id'), 68 | ); 69 | } 70 | } -------------------------------------------------------------------------------- /lib/php/components/db/mysql/models/NsSubscriber.php: -------------------------------------------------------------------------------- 1 | true), 49 | array('role, sid', 'length', 'max' => 255), 50 | array('sid_expiration', 'safe'), 51 | // The following rule is used by search(). 52 | // Please remove those attributes that should not be searched. 53 | array('id, role, user_id, sid, sid_expiration, create_date', 'safe', 'on' => 'search'), 54 | ); 55 | } 56 | 57 | /** 58 | * @return array relational rules. 59 | */ 60 | public function relations() { 61 | // NOTE: you may need to adjust the relation name and the related 62 | // class name for the relations automatically generated below. 63 | return array( 64 | 'channelSubscribers' => array(self::HAS_MANY, 'NsChannelSubscriber', 'subscriber_id'), 65 | ); 66 | } 67 | } -------------------------------------------------------------------------------- /lib/php/components/db/mysql/models/NsSubscriberChannel.php: -------------------------------------------------------------------------------- 1 | true), 51 | // The following rule is used by search(). 52 | // Please remove those attributes that should not be searched. 53 | array('id, subscriber_id, channel_id, can_send_event_from_php, can_send_event_from_js, create_date', 'safe', 'on' => 'search'), 54 | ); 55 | } 56 | 57 | /** 58 | * @return array relational rules. 59 | */ 60 | public function relations() { 61 | // NOTE: you may need to adjust the relation name and the related 62 | // class name for the relations automatically generated below. 63 | return array( 64 | 'channel' => array(self::BELONGS_TO, 'NsChannel', 'channel_id'), 65 | 'subscriber' => array(self::BELONGS_TO, 'NsSubscriber', 'subscriber_id'), 66 | ); 67 | } 68 | } -------------------------------------------------------------------------------- /lib/php/components/frames/JQuerySelector.php: -------------------------------------------------------------------------------- 1 | _selector = $selector; 23 | $this->_owner = $owner; 24 | $this->_query = array( 25 | 'selector' => $selector, 26 | 'beforeApply' => null, 27 | 'afterApply' => null, 28 | 'actions' => array() 29 | ); 30 | } 31 | 32 | /** 33 | * @param string $data 34 | * 35 | * @return JQuerySelector 36 | */ 37 | public function append($data) { 38 | if (is_string($data) && !empty($data)) { 39 | $this->createAction('append', array($data)); 40 | } 41 | return $this; 42 | } 43 | 44 | /** 45 | * @param string $data 46 | * 47 | * @return JQuerySelector 48 | */ 49 | public function prepend($data) { 50 | if ($this->isValidString($data)) { 51 | $this->createAction('prepend', array($data)); 52 | } 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param string $data 58 | * 59 | * @return JQuerySelector 60 | */ 61 | public function replaceWith($data) { 62 | if ($this->isValidString($data)) { 63 | $this->createAction('replaceWith', array($data)); 64 | } 65 | return $this; 66 | } 67 | 68 | /** 69 | * @param string $html 70 | * 71 | * @return JQuerySelector 72 | */ 73 | public function html($html) { 74 | if (is_string($html)) { 75 | $this->createAction('html', array($html)); 76 | } 77 | return $this; 78 | } 79 | 80 | /** 81 | * @param string $text 82 | * 83 | * @return JQuerySelector 84 | */ 85 | public function text($text) { 86 | if (is_string($text)) { 87 | $this->createAction('text', array($text)); 88 | } 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param array $arguments 94 | * 95 | * @return JQuerySelector 96 | */ 97 | public function slideUp(array $arguments = array()) { 98 | return $this->createAction('slideUp', $arguments); 99 | } 100 | 101 | /** 102 | * @param array $arguments 103 | * 104 | * @return JQuerySelector 105 | */ 106 | public function slideDown(array $arguments = array()) { 107 | return $this->createAction('slideDown', $arguments); 108 | } 109 | 110 | /** 111 | * @param string $class 112 | * 113 | * @return JQuerySelector 114 | */ 115 | public function addClass($class) { 116 | if ($this->isValidString($class)) { 117 | $this->createAction('addClass', array($class)); 118 | } 119 | return $this; 120 | } 121 | 122 | /** 123 | * @param string $class 124 | * 125 | * @return JQuerySelector 126 | */ 127 | public function removeClass($class) { 128 | if ($this->isValidString($class)) { 129 | $this->createAction('removeClass', array($class)); 130 | } 131 | return $this; 132 | } 133 | 134 | /** 135 | * @param string $selector 136 | * 137 | * @return JQuerySelector 138 | */ 139 | public function parents($selector) { 140 | if ($this->isValidString($selector)) { 141 | $this->createAction('parents', array($selector)); 142 | } 143 | return $this; 144 | } 145 | 146 | /** 147 | * @return JQuerySelector 148 | */ 149 | public function parent() { 150 | return $this->createAction('parent'); 151 | } 152 | 153 | /** 154 | * @return JQuerySelector 155 | */ 156 | public function children() { 157 | return $this->createAction('children'); 158 | } 159 | 160 | /** 161 | * @param string $selector 162 | * 163 | * @return JQuerySelector 164 | */ 165 | public function find($selector) { 166 | if ($this->isValidString($selector)) { 167 | $this->createAction('find', array($selector)); 168 | } 169 | return $this; 170 | } 171 | 172 | /** 173 | * @return JQuerySelector 174 | */ 175 | public function remove() { 176 | $this->createAction('remove'); 177 | return $this; 178 | } 179 | 180 | /** 181 | * @return JQuerySelector 182 | */ 183 | public function prev() { 184 | $this->createAction('prev'); 185 | return $this; 186 | } 187 | 188 | /** 189 | * @return JQuerySelector 190 | */ 191 | public function next() { 192 | $this->createAction('next'); 193 | return $this; 194 | } 195 | 196 | /** 197 | * @param string $name 198 | * @param string|integer $value 199 | * 200 | * @return JQuerySelector 201 | */ 202 | public function attr($name, $value) { 203 | if ($this->isValidString($name) && !empty($value)) { 204 | $this->createAction('attr', array($name, $value)); 205 | } 206 | return $this; 207 | } 208 | 209 | /** 210 | * @param array $styles 211 | * 212 | * @return JQuerySelector 213 | */ 214 | public function css(array $styles) { 215 | return $this->createAction('css', array($styles)); 216 | } 217 | 218 | /** 219 | * @param array $arguments 220 | * @return JQuerySelector 221 | */ 222 | public function show(array $arguments = array()) { 223 | $this->createAction('show', $arguments); 224 | return $this; 225 | } 226 | 227 | /** 228 | * @param array $arguments 229 | * @return JQuerySelector 230 | */ 231 | public function hide(array $arguments = array()) { 232 | return $this->createAction('hide', $arguments); 233 | } 234 | 235 | /** 236 | * @param string $effect 237 | * @param array $arguments 238 | * 239 | * @return JQuerySelector 240 | */ 241 | public function effect($effect, array $arguments = array()) { 242 | if ($this->isValidString($effect)) { 243 | array_unshift($arguments, $effect); 244 | $this->createAction('effect', $arguments); 245 | } 246 | return $this; 247 | } 248 | 249 | /** 250 | * @return JQuerySelector 251 | */ 252 | public function getOwner() { 253 | return $this->_owner; 254 | } 255 | 256 | /** 257 | * Set name of javascript function which will be called 258 | * BEFORE apply action 259 | * 260 | * @param string $functionName 261 | * @return JQuerySelector 262 | */ 263 | public function beforeApply($functionName) { 264 | if (is_string($functionName) && !empty($functionName)) { 265 | $this->_query['beforeApply'] = $functionName; 266 | } 267 | return $this; 268 | } 269 | 270 | /** 271 | * Set name of javascript function which will be called 272 | * AFTER apply action 273 | * 274 | * @param string $functionName 275 | * @return JQuerySelector 276 | */ 277 | public function afterApply($functionName) { 278 | if (is_string($functionName) && !empty($functionName)) { 279 | $this->_query['afterApply'] = $functionName; 280 | } 281 | return $this; 282 | } 283 | 284 | /** 285 | * @return array 286 | */ 287 | public function getQuery() { 288 | return $this->_query; 289 | } 290 | 291 | /** 292 | * @param $string 293 | * 294 | * @return bool 295 | */ 296 | private function isValidString($string) { 297 | return is_string($string) && !empty($string); 298 | } 299 | 300 | /** 301 | * @param string $action 302 | * @param array $arguments 303 | * 304 | * @return JQuerySelector 305 | */ 306 | private function createAction($action, array $arguments = array()) { 307 | $this->_query['actions'][] = array( 308 | 'action' => $action, 309 | 'arguments' => $arguments 310 | ); 311 | return $this; 312 | } 313 | } -------------------------------------------------------------------------------- /lib/php/console/ConsoleInterface.php: -------------------------------------------------------------------------------- 1 | ' . $logFile . ' 2>&1 & echo $!'; 27 | exec($command, $op); 28 | return (int) $op[0]; 29 | } 30 | 31 | /** 32 | * Check if process with pid in progress 33 | * 34 | * @param $pid 35 | * 36 | * @return boolean 37 | */ 38 | public function isInProgress($pid) { 39 | $command = 'ps -p ' . $pid; 40 | exec($command,$op); 41 | if (!isset($op[1])) { 42 | return false; 43 | } else { 44 | return true; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /lib/php/console/WinConsole.php: -------------------------------------------------------------------------------- 1 | parseProccess($pid); 17 | exec($command); 18 | } 19 | 20 | /** 21 | * @param string $command 22 | * @param string $logFile 23 | * 24 | * @return integer pid 25 | */ 26 | public function startServer($command, $logFile) { 27 | $command = 'start /b ' . $command . ' > ' . $logFile; 28 | $process = proc_open($command, array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w")), $pipes); 29 | $status = proc_get_status($process); 30 | $pid = $status['pid']; 31 | if ($pid) { 32 | return $pid; 33 | } else { 34 | return false; 35 | } 36 | } 37 | 38 | /** 39 | * @param $pid 40 | * 41 | * @return boolean 42 | */ 43 | public function isInProgress($pid) { 44 | $findProccess = $this->parseProccess($pid); 45 | if ($findProccess) { 46 | return true; 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | protected function parseProccess($pid) { 53 | $output = array_filter(explode(" ", shell_exec("wmic process where ParentProcessId=\"$pid\" get processid"))); 54 | array_pop($output); 55 | return (int)end($output); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /lib/php/frames/AFrame.php: -------------------------------------------------------------------------------- 1 | _nodeSocket = $nodeSocket; 49 | $this->_createContainer(); 50 | $this->init(); 51 | } 52 | 53 | /** 54 | * @return mixed 55 | */ 56 | public function getId() { 57 | if (isset($this->_id)) { 58 | return $this->_id; 59 | } 60 | return $this->_id = time() . ':' .uniqid(); 61 | } 62 | 63 | public function send() { 64 | 65 | // call before send event 66 | if ($this->isValid() && $this->beforeSend()) { 67 | 68 | // check - if frame is not multiple 69 | // then send frame 70 | if (!$this->_isAsMultiple) { 71 | 72 | // prepare frame to send 73 | $this->prepareFrame(); 74 | 75 | // emit 76 | $this->emit(); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * Will be called before emit and after isValid & beforeSend methods 83 | */ 84 | public function prepareFrame() {} 85 | 86 | /** 87 | * @param array $data 88 | * 89 | * @return AFrame 90 | */ 91 | public function setData(array $data) { 92 | $this->_container['data'] = $data; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function getEncodedFrame() { 100 | return json_encode($this->getFrame()); 101 | } 102 | 103 | /** 104 | * @return array 105 | */ 106 | final public function getFrame() { 107 | return $this->_container; 108 | } 109 | 110 | /** 111 | * @return array 112 | */ 113 | final public function getData() { 114 | return $this->_container['data']; 115 | } 116 | 117 | /** 118 | * @return array 119 | */ 120 | final public function getMeta() { 121 | return $this->_container['meta']; 122 | } 123 | 124 | /** 125 | * @return array 126 | */ 127 | final public function getMetaData() { 128 | return $this->_container['meta']['data']; 129 | } 130 | 131 | /** 132 | * @param Multiple $multipleFrame 133 | * 134 | * @return AFrame 135 | */ 136 | final public function setAsMultiple(Multiple $multipleFrame) { 137 | if (!$this->_isAsMultiple) { 138 | $multipleFrame->addFrame($this); 139 | $this->_isAsMultiple = true; 140 | } 141 | return $this; 142 | } 143 | 144 | /** 145 | * @return bool 146 | */ 147 | final public function isAsMultiple() { 148 | return $this->_isAsMultiple; 149 | } 150 | 151 | /** 152 | * @return bool 153 | */ 154 | protected function beforeSend() { 155 | return true; 156 | } 157 | 158 | protected function emit() { 159 | $client = $this->createClient(); 160 | $client->origin = $this->_nodeSocket->getOrigin(); 161 | $client->sendCookie = true; 162 | $client->cookie = implode('; ', array( 163 | 'PHPSESSID=' . \Yii::$app->session->id, 164 | 'expires=' . (time() + $this->_nodeSocket->cookieLifeTime) 165 | )); 166 | $client->setHandshakeTimeout($this->_nodeSocket->handshakeTimeout); 167 | $client->init(); 168 | 169 | $client 170 | ->createFrame() 171 | ->endPoint('/server') 172 | ->emit($this->getType(), $this->getFrame()); 173 | 174 | $client->close(); 175 | } 176 | 177 | /** 178 | * @return \ElephantIO\Client 179 | */ 180 | protected function createClient() { 181 | return new \ElephantIO\Client( 182 | sprintf('http://%s:%s', $this->_nodeSocket->host, $this->_nodeSocket->port), 183 | 'socket.io', 184 | 1, 185 | false 186 | ); 187 | } 188 | 189 | protected function init() {} 190 | 191 | /** 192 | * @param array $data 193 | * 194 | * @return AFrame 195 | */ 196 | final protected function setMetaData(array $data) { 197 | $this->_container['meta']['data'] = $data; 198 | return $this; 199 | } 200 | 201 | /** 202 | * @param string|int $key 203 | * @param mixed $value 204 | * 205 | * @return AFrame 206 | */ 207 | final protected function addMetaData($key, $value) { 208 | if (is_string($key) || is_int($key)) { 209 | $this->_container['meta']['data'][$key] = $value; 210 | } 211 | return $this; 212 | } 213 | 214 | /** 215 | * @param $key 216 | * 217 | * @return bool 218 | */ 219 | final protected function hasMetaData($key) { 220 | return array_key_exists($key, $this->_container['meta']['data']); 221 | } 222 | 223 | /** 224 | * @param string|int $key 225 | * 226 | * @return AFrame 227 | */ 228 | final protected function removeMetaData($key) { 229 | if ($this->hasMetaData($key)) { 230 | unset($this->_container['meta']['data'][$key]); 231 | } 232 | return $this; 233 | } 234 | 235 | private function _createContainer() { 236 | $this->_container = array( 237 | 'meta' => array( 238 | 'id' => $this->getId(), 239 | 'type' => $this->getType(), 240 | 'sid' => \Yii::$app->session->id, 241 | 'data' => array() 242 | ), 243 | 'data' => array() 244 | ); 245 | } 246 | 247 | /** 248 | * (PHP 5 >= 5.0.0)
249 | * Whether a offset exists 250 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 251 | * 252 | * @param mixed $offset

253 | * An offset to check for. 254 | *

255 | * 256 | * @return boolean true on success or false on failure. 257 | *

258 | *

259 | * The return value will be casted to boolean if non-boolean was returned. 260 | */ 261 | public function offsetExists($offset) { 262 | return array_key_exists($offset, $this->_container['data']); 263 | } 264 | 265 | /** 266 | * (PHP 5 >= 5.0.0)
267 | * Offset to retrieve 268 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 269 | * 270 | * @param mixed $offset

271 | * The offset to retrieve. 272 | *

273 | * 274 | * @return mixed Can return all value types. 275 | */ 276 | public function offsetGet($offset) { 277 | return array_key_exists($offset, $this->_container['data']) ? $this->_container['data'][$offset] : null; 278 | } 279 | 280 | /** 281 | * (PHP 5 >= 5.0.0)
282 | * Offset to set 283 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 284 | * 285 | * @param mixed $offset

286 | * The offset to assign the value to. 287 | *

288 | * @param mixed $value

289 | * The value to set. 290 | *

291 | * 292 | * @return void 293 | */ 294 | public function offsetSet($offset, $value) { 295 | if (is_null($offset)) { 296 | $this->_container['data'][] = $offset; 297 | } else { 298 | $this->_container['data'][$offset] = $value; 299 | } 300 | } 301 | 302 | /** 303 | * (PHP 5 >= 5.0.0)
304 | * Offset to unset 305 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 306 | * 307 | * @param mixed $offset

308 | * The offset to unset. 309 | *

310 | * 311 | * @return void 312 | */ 313 | public function offsetUnset($offset) { 314 | unset($this->_container['data'][$offset]); 315 | } 316 | } -------------------------------------------------------------------------------- /lib/php/frames/Authentication.php: -------------------------------------------------------------------------------- 1 | hasMetaData('uid'); 11 | } 12 | 13 | /** 14 | * @return string 15 | */ 16 | public function getType() { 17 | return 'auth'; 18 | } 19 | 20 | /** 21 | * @param integer $id 22 | * 23 | * @return $this 24 | */ 25 | public function setUserId($id) { 26 | if ($id && is_numeric($id)) { 27 | $this->addMetaData('uid', $id); 28 | } 29 | return $this; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/php/frames/ChannelEvent.php: -------------------------------------------------------------------------------- 1 | addMetaData('action', $action); 21 | } 22 | return $this; 23 | } 24 | 25 | /** 26 | * @return bool 27 | */ 28 | public function isValid() { 29 | return $this->hasMetaData('action'); 30 | } 31 | } -------------------------------------------------------------------------------- /lib/php/frames/Event.php: -------------------------------------------------------------------------------- 1 | addMetaData('event', $eventName); 21 | } 22 | return $this; 23 | } 24 | 25 | /** 26 | * @param string $room 27 | * 28 | * @return Event 29 | */ 30 | public function setRoom($room) { 31 | if ((is_string($room) || is_numeric($room)) && !empty($room)) { 32 | $this->addMetaData('room', $room); 33 | } 34 | return $this; 35 | } 36 | 37 | /** 38 | * @param string $channel 39 | * 40 | * @return Event 41 | */ 42 | public function setChannel($channel) { 43 | if ((is_string($channel) || is_numeric($channel)) && !empty($channel)) { 44 | $this->addMetaData('channel', $channel); 45 | } 46 | return $this; 47 | } 48 | 49 | protected function init() { 50 | $this->setEventName('undefined'); 51 | } 52 | 53 | /** 54 | * @return bool 55 | */ 56 | public function isValid() { 57 | return $this->hasMetaData('event'); 58 | } 59 | } -------------------------------------------------------------------------------- /lib/php/frames/FrameFactory.php: -------------------------------------------------------------------------------- 1 | _nodeSocket = $transport; 27 | } 28 | 29 | /** 30 | * @return Event 31 | */ 32 | public function createEventFrame() { 33 | return new Event($this->_nodeSocket); 34 | } 35 | 36 | /** 37 | * @return ChannelEvent 38 | */ 39 | public function createChannelEventFrame() { 40 | return new ChannelEvent($this->_nodeSocket); 41 | } 42 | 43 | /** 44 | * @return Multiple 45 | */ 46 | public function createMultipleFrame() { 47 | return new Multiple($this->_nodeSocket); 48 | } 49 | 50 | /** 51 | * @return PublicData 52 | */ 53 | public function createPublicDataFrame() { 54 | return new PublicData($this->_nodeSocket); 55 | } 56 | 57 | /** 58 | * @return Invoke 59 | */ 60 | public function createInvokeFrame() { 61 | return new Invoke($this->_nodeSocket); 62 | } 63 | 64 | /** 65 | * @return JQuery 66 | */ 67 | public function createJQueryFrame() { 68 | return new JQuery($this->_nodeSocket); 69 | } 70 | 71 | /** 72 | * @return Authentication 73 | */ 74 | public function createAuthenticationFrame() { 75 | return new Authentication($this->_nodeSocket); 76 | } 77 | 78 | /** 79 | * @return Authentication 80 | */ 81 | public function createLogoutFrame() { 82 | return new LogoutFrame($this->_nodeSocket); 83 | } 84 | 85 | /** 86 | * @return UserEvent 87 | */ 88 | public function createUserEventFrame() { 89 | return new UserEvent($this->_nodeSocket); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/php/frames/IFrameFactory.php: -------------------------------------------------------------------------------- 1 | _functions; 27 | } 28 | 29 | /** 30 | * Return list of methods for invocation 31 | * 32 | * @return array 33 | */ 34 | public function getMethods() { 35 | return $this->_methods; 36 | } 37 | 38 | /** 39 | * @param string $functionName 40 | * @param array $arguments 41 | * @param null|string $scope 42 | * 43 | * @return Invoke 44 | */ 45 | public function invokeFunction($functionName, array $arguments = array(), $scope = null) { 46 | if (is_string($functionName) && !empty($functionName)) { 47 | $this->_functions[] = array( 48 | 'function' => $functionName, 49 | 'arguments' => $arguments, 50 | 'scope' => is_string($scope) ? $scope : null 51 | ); 52 | } 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param string $object 58 | * @param string $method 59 | * @param array $arguments 60 | * @param string|null $scope 61 | * @return Invoke 62 | */ 63 | public function invokeMethodOfObject($object, $method, array $arguments = array(), $scope = null) { 64 | if (is_string($object) && is_string($method) && !empty($object) && !empty($method)) { 65 | $this->_methods[] = array( 66 | 'object' => $object, 67 | 'method' => $method, 68 | 'arguments' => $arguments, 69 | 'scope' => is_string($scope) ? $scope : null 70 | ); 71 | } 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return bool 77 | */ 78 | public function isValid() { 79 | return !empty($this->_functions) || !empty($this->_methods); 80 | } 81 | 82 | public function prepareFrame() { 83 | parent::prepareFrame(); 84 | $this->setData(array( 85 | 'functions' => $this->_functions, 86 | 'methods' => $this->_methods 87 | )); 88 | } 89 | } -------------------------------------------------------------------------------- /lib/php/frames/JQuery.php: -------------------------------------------------------------------------------- 1 | _queries[] = $query; 33 | return $query; 34 | } 35 | 36 | /** 37 | * @return bool 38 | */ 39 | public function isValid() { 40 | return !empty($this->_queries); 41 | } 42 | 43 | public function prepareFrame() { 44 | $data = array(); 45 | foreach ($this->_queries as $query) { 46 | $data[] = $query->getQuery(); 47 | } 48 | $this->setData($data); 49 | } 50 | } -------------------------------------------------------------------------------- /lib/php/frames/LogoutFrame.php: -------------------------------------------------------------------------------- 1 | setEventName('user.join'); 13 | * $eventFrame['userId'] = 12; 14 | * 15 | * $subscriptionFrame = new Subscription(); 16 | * $subscriptionFrame->subscribe('users.channel'); 17 | * 18 | * $multiple = new Multiple() 19 | * $multiple 20 | * ->addFrame($eventFrame) 21 | * ->addFrame($subscriptionFrame) 22 | * ->send(); 23 | * 24 | * @package YiiNodeSocket\Frame 25 | */ 26 | class Multiple extends AFrame implements IFrameFactory { 27 | 28 | /** 29 | * @var array 30 | */ 31 | protected $_frames = array(); 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getType() { 37 | return self::TYPE_MULTIPLE_FRAME; 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | public function isValid() { 44 | return !empty($this->_frames); 45 | } 46 | 47 | /** 48 | * @return ChannelEvent 49 | */ 50 | public function createChannelEventFrame() { 51 | return $this 52 | ->_nodeSocket->getFrameFactory() 53 | ->createChannelEventFrame() 54 | ->setAsMultiple($this); 55 | } 56 | 57 | /** 58 | * @return Event 59 | */ 60 | public function createEventFrame() { 61 | return $this 62 | ->_nodeSocket->getFrameFactory() 63 | ->createEventFrame() 64 | ->setAsMultiple($this); 65 | } 66 | 67 | /** 68 | * @return Multiple 69 | */ 70 | public function createMultipleFrame() { 71 | return $this; 72 | } 73 | 74 | /** 75 | * @return PublicData 76 | */ 77 | public function createPublicDataFrame() { 78 | return $this 79 | ->_nodeSocket->getFrameFactory() 80 | ->createPublicDataFrame() 81 | ->setAsMultiple($this); 82 | } 83 | 84 | /** 85 | * @return Invoke 86 | */ 87 | public function createInvokeFrame() { 88 | return $this 89 | ->_nodeSocket->getFrameFactory() 90 | ->createInvokeFrame() 91 | ->setAsMultiple($this); 92 | } 93 | 94 | /** 95 | * @return JQuery 96 | */ 97 | public function createJQueryFrame() { 98 | return $this 99 | ->_nodeSocket->getFrameFactory() 100 | ->createJQueryFrame() 101 | ->setAsMultiple($this); 102 | } 103 | 104 | /** 105 | * @return Authentication 106 | */ 107 | public function createAuthenticationFrame() { 108 | return $this 109 | ->_nodeSocket->getFrameFactory() 110 | ->createAuthenticationFrame() 111 | ->setAsMultiple($this); 112 | } 113 | 114 | /** 115 | * @return LogoutFrame 116 | */ 117 | public function createLogoutFrame() { 118 | return $this 119 | ->_nodeSocket->getFrameFactory() 120 | ->createLogoutFrame() 121 | ->setAsMultiple($this); 122 | } 123 | 124 | /** 125 | * @return UserEvent 126 | */ 127 | public function createUserEventFrame() { 128 | return $this 129 | ->_nodeSocket->getFrameFactory() 130 | ->createUserEventFrame() 131 | ->setAsMultiple($this); 132 | } 133 | 134 | 135 | /** 136 | * @param AFrame $frame 137 | * 138 | * @return Multiple 139 | */ 140 | public function addFrame(AFrame $frame) { 141 | if (!array_key_exists($frame->getType(), $this->_frames)) { 142 | $this->_frames[$frame->getType()] = array(); 143 | } 144 | if (!array_key_exists($frame->getId(), $this->_frames[$frame->getType()])) { 145 | $this->_frames[$frame->getType()][$frame->getId()] = $frame; 146 | } 147 | return $this; 148 | } 149 | 150 | protected function beforeSend() { 151 | $data = array(); 152 | $metaData = array(); 153 | /** @var AFrame $frame */ 154 | foreach ($this->_frames as $type => $f) { 155 | $data[$type] = array(); 156 | $metaData[$type] = array(); 157 | foreach ($f as $id => $frame) { 158 | 159 | // check if frame is valid frame 160 | if ($frame->isValid()) { 161 | 162 | // prepare frame 163 | $frame->prepareFrame(); 164 | 165 | // collect frame data 166 | $data[$type][$id] = $frame->getData(); 167 | $metaData[$type][$id] = $frame->getMeta(); 168 | } 169 | } 170 | if (empty($data[$type]) && empty($metaData[$type])) { 171 | unset( 172 | $data[$type], 173 | $metaData[$type] 174 | ); 175 | } 176 | } 177 | if (empty($data) && empty($metaData)) { 178 | return false; 179 | } 180 | $this->setData($data); 181 | $this->setMetaData($metaData); 182 | return true; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/php/frames/PublicData.php: -------------------------------------------------------------------------------- 1 | hasMetaData('key'); 18 | } 19 | 20 | /** 21 | * If $lifetime > 0 than data will be destroyed after that limit 22 | * 23 | * @param integer $lifetime 24 | * 25 | * @return PublicData 26 | */ 27 | public function setLifeTime($lifetime) { 28 | if (is_int($lifetime) || is_numeric($lifetime)) { 29 | $this->addMetaData('lifetime', (int) $lifetime); 30 | } 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param $key 36 | * 37 | * @return PublicData 38 | */ 39 | public function setKey($key) { 40 | if (is_string($key)) { 41 | $this->addMetaData('key', $key); 42 | } 43 | return $this; 44 | } 45 | 46 | protected function init() { 47 | $this->setLifeTime(0); 48 | } 49 | } -------------------------------------------------------------------------------- /lib/php/frames/RuntimeServerConfiguration.php: -------------------------------------------------------------------------------- 1 | hasMetaData('uid'); 18 | } 19 | 20 | /** 21 | * @param integer|array $id 22 | * 23 | * @return UserEvent 24 | */ 25 | public function setUserId($id) { 26 | if (!is_array($id)) { 27 | $id = (array) $id; 28 | } 29 | $this->addMetaData('uid', $id); 30 | return $this; 31 | } 32 | } -------------------------------------------------------------------------------- /lib/php/models/AModel.php: -------------------------------------------------------------------------------- 1 | _isNewRecord = ($scenario == 'insert'); 48 | $this->init(); 49 | $this->afterConstruct(); 50 | } 51 | 52 | /** 53 | * @param string $scenario 54 | * 55 | * @return AModel 56 | */ 57 | final public function newInstance($scenario = 'insert') { 58 | $class = get_class($this); 59 | $model = new $class($scenario); 60 | return $model; 61 | } 62 | 63 | /** 64 | * @param array $attributes 65 | */ 66 | final public function load(array $attributes) { 67 | if ($this->beforeLoad($attributes)) { 68 | $this->setAttributes($attributes); 69 | $this->afterLoad(); 70 | } 71 | } 72 | 73 | protected function init() {} 74 | 75 | /** 76 | * @return \NodeSocket 77 | */ 78 | public function getNodeSocket() { 79 | return \Yii::app()->nodeSocket; 80 | } 81 | 82 | /** 83 | * @return DriverInterface 84 | */ 85 | public function getDbDriver() { 86 | return $this->getNodeSocket()->getDb()->getDriver(); 87 | } 88 | 89 | /** 90 | * @return bool 91 | */ 92 | public function getIsNewRecord() { 93 | return $this->_isNewRecord; 94 | } 95 | 96 | /** 97 | * @param \CModelEvent $event 98 | */ 99 | public function onBeforeSave(\CModelEvent $event) { 100 | $this->raiseEvent('onBeforeSave', $event); 101 | } 102 | 103 | /** 104 | * @param \CModelEvent $event 105 | */ 106 | public function onAfterSave(\CModelEvent $event) { 107 | $this->raiseEvent('onAfterSave', $event); 108 | } 109 | 110 | /** 111 | * @param \CModelEvent $event 112 | */ 113 | public function onBeforeDelete(\CModelEvent $event) { 114 | $this->raiseEvent('onBeforeDelete', $event); 115 | } 116 | 117 | /** 118 | * @param \CModelEvent $event 119 | */ 120 | public function onAfterDelete(\CModelEvent $event) { 121 | $this->raiseEvent('onAfterDelete', $event); 122 | } 123 | 124 | /** 125 | * @return array 126 | */ 127 | public function rules() { 128 | return array( 129 | array('id', 'length', 'allowEmpty' => true), 130 | array('id', 'safe') 131 | ); 132 | } 133 | 134 | /** 135 | * @return bool 136 | */ 137 | public function save() { 138 | if ($this->beforeSave()) { 139 | $result = false; 140 | if ($this->validate()) { 141 | $result = $this->getDbDriver()->save($this); 142 | $this->afterSave(); 143 | if ($result) { 144 | $this->_isNewRecord = false; 145 | } 146 | } 147 | return $result; 148 | } 149 | return false; 150 | } 151 | 152 | /** 153 | * @return bool 154 | */ 155 | public function delete() { 156 | if ($this->beforeDelete()) { 157 | $result = $this->getDbDriver()->delete($this); 158 | if ($result) { 159 | $this->afterDelete(); 160 | } 161 | return $result; 162 | } 163 | return false; 164 | } 165 | 166 | /** 167 | * @return bool 168 | */ 169 | public function refresh() { 170 | return $this->getDbDriver()->refresh($this); 171 | } 172 | 173 | /** 174 | * @param int $pk 175 | * 176 | * @return null|AModel 177 | */ 178 | public function findByPk($pk) { 179 | return $this->getDbDriver()->findByPk($pk, $this); 180 | } 181 | 182 | /** 183 | * @param array $pk 184 | * 185 | * @return AModel[] 186 | */ 187 | public function findAllByPk(array $pk) { 188 | return $this->getDbDriver()->findAllByPk($pk, $this); 189 | } 190 | 191 | /** 192 | * @param array $attributes 193 | * 194 | * @return AModel[] 195 | */ 196 | public function findAllByAttributes(array $attributes) { 197 | return $this->getDbDriver()->findAllByAttributes($attributes, $this); 198 | } 199 | 200 | /** 201 | * @param array $attributes 202 | * 203 | * @return AModel 204 | */ 205 | public function findByAttributes(array $attributes) { 206 | return $this->getDbDriver()->findByAttributes($attributes, $this); 207 | } 208 | 209 | /** 210 | * Returns the list of attribute names of the model. 211 | * @return array list of attribute names. 212 | */ 213 | public function attributeNames() { 214 | return array( 215 | 'id' 216 | ); 217 | } 218 | 219 | protected function beforeSave() { 220 | $event = new \CModelEvent($this); 221 | $this->onBeforeSave($event); 222 | return $event->isValid; 223 | } 224 | 225 | protected function afterSave() { 226 | $modelEvent = new \CModelEvent($this); 227 | $this->onAfterSave($modelEvent); 228 | if ($modelEvent->isValid) { 229 | $event = $this->getNodeSocket()->getFrameFactory()->createChannelEventFrame(); 230 | $event 231 | ->setAction('save.' . get_class($this)) 232 | ->setData($this->getAttributes()) 233 | ->send(); 234 | } 235 | } 236 | 237 | protected function beforeDelete() { 238 | $event = new \CModelEvent($this); 239 | $this->onBeforeDelete($event); 240 | return $event->isValid; 241 | } 242 | 243 | protected function afterDelete() { 244 | $modelEvent = new \CModelEvent($this); 245 | $this->onAfterDelete($modelEvent); 246 | if ($modelEvent->isValid) { 247 | $event = $this->getNodeSocket()->getFrameFactory()->createChannelEventFrame(); 248 | $event 249 | ->setAction('delete.' . get_class($this)) 250 | ->setData($this->getAttributes()) 251 | ->send(); 252 | } 253 | } 254 | 255 | protected function beforeLoad(array $attributes) { 256 | return true; 257 | } 258 | 259 | protected function afterLoad() {} 260 | } -------------------------------------------------------------------------------- /lib/php/models/Channel.php: -------------------------------------------------------------------------------- 1 | 2), 73 | array('properties', 'length', 'max' => 65000, 'allowEmpty' => true), 74 | array('subscriber_source, event_source', 'numerical', 'integerOnly' => true), 75 | array('subscriber_source, event_source', 'in', 'range' => $this->getSourceList()), 76 | array('allowed_roles', 'length', 'min' => 1, 'allowEmpty' => true), 77 | array('create_date', 'safe') 78 | )); 79 | } 80 | 81 | /** 82 | * @param Subscriber $subscriber 83 | * @param array $subscribeOptions 84 | * 85 | * @return bool 86 | */ 87 | public function subscribe(Subscriber $subscriber, array $subscribeOptions = array()) { 88 | return SubscriberChannel::model()->createLink($this, $subscriber, $subscribeOptions); 89 | } 90 | 91 | /** 92 | * @param Subscriber $subscriber 93 | * 94 | * @return bool 95 | */ 96 | public function unSubscribe(Subscriber $subscriber) { 97 | return SubscriberChannel::model()->destroyLink($this, $subscriber); 98 | } 99 | 100 | /** 101 | * @param bool $refresh 102 | * 103 | * @return Subscriber[] 104 | */ 105 | public function getSubscribers($refresh = false) { 106 | return SubscriberChannel::model()->getSubscribers($this, $refresh); 107 | } 108 | 109 | /** 110 | * @param string $name 111 | * 112 | * @return \YiiNodeSocket\Frames\Event 113 | */ 114 | public function createEvent($name = null) { 115 | $event = $this->getNodeSocket()->getFrameFactory()->createEventFrame(); 116 | if ($name) { 117 | $event->setEventName($name); 118 | } 119 | $event->setChannel($this->name); 120 | return $event; 121 | } 122 | 123 | /** 124 | * Returns the list of attribute names of the model. 125 | * @return array list of attribute names. 126 | */ 127 | public function attributeNames() { 128 | return array_merge(parent::attributeNames(), array( 129 | 'name', 130 | 'properties', 131 | 'is_authentication_required', 132 | 'allowed_roles', 133 | 'subscriber_source', 134 | 'event_source', 135 | 'create_date' 136 | )); 137 | } 138 | 139 | /** 140 | * @return bool 141 | */ 142 | public function validateUniqueName() { 143 | if (!empty($this->name)) { 144 | $exists = $this->findByAttributes(array( 145 | 'name' => $this->name 146 | )); 147 | if ($exists) { 148 | if (!$this->getIsNewRecord() && $exists->id == $this->id) { 149 | return true; 150 | } 151 | $this->addError('name', 'Channel name should be unique'); 152 | return false; 153 | } 154 | } 155 | return true; 156 | } 157 | 158 | /** 159 | * @return bool 160 | */ 161 | protected function beforeValidate() { 162 | if (is_array($this->properties) || is_object($this->properties)) { 163 | $this->properties = \CJSON::encode($this->properties); 164 | } else if (!is_string($this->properties)) { 165 | $this->properties = ''; 166 | } 167 | return parent::beforeValidate(); 168 | } 169 | 170 | /** 171 | * @return bool 172 | */ 173 | protected function beforeSave() { 174 | $this->is_authentication_required = (int) $this->is_authentication_required; 175 | if (is_array($this->allowed_roles)) { 176 | $this->allowed_roles = implode(',', $this->allowed_roles); 177 | } else if (!is_string($this->allowed_roles)) { 178 | $this->allowed_roles = ''; 179 | } else { 180 | $this->allowed_roles = implode(',', array_map('trim', explode(',', $this->allowed_roles))); 181 | } 182 | return parent::beforeSave(); 183 | } 184 | 185 | protected function afterLoad() { 186 | if ($this->properties) { 187 | $this->properties = \CJSON::decode($this->properties); 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /lib/php/models/Subscriber.php: -------------------------------------------------------------------------------- 1 | 1), 47 | array('user_id', 'numerical', 'integerOnly' => true), 48 | array('user_id, sid', 'validateUserIdOrSid'), 49 | array('sid', 'length', 'min' => 5, 'allowEmpty' => true), 50 | array('sid_expiration', 'numerical', 'integerOnly' => true, 'allowEmpty' => true) 51 | )); 52 | } 53 | 54 | /** 55 | * Returns the list of attribute names of the model. 56 | * @return array list of attribute names. 57 | */ 58 | public function attributeNames() { 59 | return array_merge(parent::attributeNames(), array( 60 | 'role', 61 | 'user_id', 62 | 'sid', 63 | 'sid_expiration' 64 | )); 65 | } 66 | 67 | /** 68 | * @param bool $refresh 69 | * 70 | * @return Channel[] 71 | */ 72 | public function getChannels($refresh = false) { 73 | if ($this->_channels && !$refresh) { 74 | return $this->_channels; 75 | } 76 | return $this->_channels = $this->getDbDriver()->findByAttributes(array( 77 | 'subscriber_id' => $this->id 78 | ), Subscriber::model()); 79 | } 80 | 81 | /** 82 | * @param $attribute 83 | * 84 | * @return bool 85 | */ 86 | public function validateUserIdOrSid($attribute) { 87 | $value = $this->$attribute; 88 | if (empty($value)) { 89 | if ($attribute == 'sid' && empty($this->user_id) || ($attribute == 'user_id' && empty($this->sid))) { 90 | $this->addError($attribute, sprintf('User id or sid are required!')); 91 | return false; 92 | } 93 | $attribute = $attribute == 'sid' ? 'user_id' : 'sid'; 94 | } 95 | // check for unique 96 | $exists = $this->findByAttributes(array( 97 | $attribute => $this->$attribute 98 | )); 99 | if ($exists) { 100 | if ($this->getIsNewRecord() || $exists->id != $this->id) { 101 | $this->addError($attribute, sprintf('%s should be unique!', $attribute)); 102 | return false; 103 | } 104 | } 105 | return true; 106 | } 107 | 108 | /** 109 | * @return bool 110 | */ 111 | protected function beforeValidate() { 112 | if ($this->sid_expiration && !is_numeric($this->sid_expiration)) { 113 | $this->sid_expiration = strtotime($this->sid_expiration); 114 | } 115 | return parent::beforeValidate(); 116 | } 117 | } -------------------------------------------------------------------------------- /lib/php/models/SubscriberChannel.php: -------------------------------------------------------------------------------- 1 | getIsNewRecord() || $subscriber->getIsNewRecord()) { 62 | // if yes, return false 63 | return false; 64 | } 65 | 66 | // maybe link exists 67 | $link = self::model()->findByAttributes(array( 68 | 'channel_id' => $channel->id, 69 | 'subscriber_id' => $subscriber->id 70 | )); 71 | 72 | if ($link) { 73 | // add channel and subscriber to cache 74 | self::_addToCache($channel, $subscriber); 75 | // if yes, try update options 76 | /* @var SubscriberChannel $link */ 77 | if (!empty($options)) { 78 | $link->setOptions($options); 79 | return $link->save(); 80 | } 81 | // else return only true 82 | return true; 83 | } 84 | 85 | // create link 86 | $link = new SubscriberChannel(); 87 | $link->channel_id = $channel->id; 88 | $link->subscriber_id = $subscriber->id; 89 | $link->setOptions($options); 90 | if ($link->save()) { 91 | // add to cache 92 | self::_addToCache($channel, $subscriber); 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | /** 99 | * @param Channel $channel 100 | * @param Subscriber $subscriber 101 | * 102 | * @return bool 103 | */ 104 | public static function destroyLink(Channel $channel, Subscriber $subscriber) { 105 | if ($channel->getIsNewRecord() || $subscriber->getIsNewRecord()) { 106 | return false; 107 | } 108 | $link = self::model()->findByAttributes(array( 109 | 'channel_id' => $channel->id, 110 | 'subscriber_id' => $subscriber->id 111 | )); 112 | if ($link) { 113 | if ($link->delete()) { 114 | if (isset(self::$_channelSubscribers[$channel->id]) && isset(self::$_channelSubscribers[$channel->id][$subscriber->id])) { 115 | unset(self::$_channelSubscribers[$channel->id][$subscriber->id]); 116 | } 117 | if (isset(self::$_subscriberChannels[$subscriber->id]) && isset(self::$_subscriberChannels[$subscriber->id][$channel->id])) { 118 | unset(self::$_subscriberChannels[$subscriber->id][$channel->id]); 119 | } 120 | return true; 121 | } 122 | return false; 123 | } 124 | return true; 125 | } 126 | 127 | /** 128 | * @param array $options 129 | */ 130 | public function setOptions(array $options = array()) { 131 | foreach (array('can_send_event_from_php', 'can_send_event_from_js') as $attribute) { 132 | if (array_key_exists($attribute, $options)) { 133 | $this->$attribute = $options[$attribute]; 134 | } 135 | } 136 | } 137 | 138 | public function rules() { 139 | return array( 140 | array('subscriber_id, channel_id', 'required'), 141 | array('subscriber_id, channel_id', 'length', 'min' => 1, 'max' => 255), 142 | array('can_send_event_from_php, can_send_event_from_js', 'numerical', 'integerOnly' => true) 143 | ); 144 | } 145 | 146 | /** 147 | * Returns the list of attribute names of the model. 148 | * @return array list of attribute names. 149 | */ 150 | public function attributeNames() { 151 | return array( 152 | 'subscriber_id', 153 | 'channel_id', 154 | 'can_send_event_from_php', 155 | 'can_send_event_from_js', 156 | 'create_date' 157 | ); 158 | } 159 | 160 | /** 161 | * @param Channel $channel 162 | * @param bool $refresh 163 | * 164 | * @return AModel[] 165 | */ 166 | public function getSubscribers(Channel $channel, $refresh = false) { 167 | if ($channel->getIsNewRecord()) { 168 | return array(); 169 | } 170 | if (array_key_exists($channel->id, self::$_channelSubscribers) && !$refresh) { 171 | return self::$_channelSubscribers[$channel->id]; 172 | } 173 | $links = SubscriberChannel::model()->findAllByAttributes(array( 174 | 'channel_id' => $channel->id 175 | )); 176 | $subscriberId = array(); 177 | foreach ($links as $link) { 178 | $subscriberId[] = $link->subscriber_id; 179 | } 180 | $subscribers = Subscriber::model()->findAllByPk($subscriberId); 181 | foreach ($subscribers as $subscriber) { 182 | self::_addToCache($channel, $subscriber); 183 | } 184 | return $subscribers; 185 | } 186 | 187 | /** 188 | * @param Subscriber $subscriber 189 | * @param bool $refresh 190 | * 191 | * @return AModel[] 192 | */ 193 | public function getChannels(Subscriber $subscriber, $refresh = false) { 194 | if ($subscriber->getIsNewRecord()) { 195 | return array(); 196 | } 197 | if (array_key_exists($subscriber->id, self::$_subscriberChannels) && !$refresh) { 198 | return self::$_subscriberChannels[$subscriber->id]; 199 | } 200 | $links = $this->findAllByAttributes(array( 201 | 'subscriber_id' => $subscriber->id 202 | )); 203 | $channelId = array(); 204 | foreach ($links as $link) { 205 | $channelId[] = $link->id; 206 | } 207 | $channels = Channel::model()->findAllByPk($channelId); 208 | foreach ($channels as $channel) { 209 | self::_addToCache($channel, $subscriber); 210 | } 211 | return $channels; 212 | } 213 | 214 | /** 215 | * @return bool 216 | */ 217 | protected function beforeValidate() { 218 | $this->can_send_event_from_js = (int) $this->can_send_event_from_js; 219 | $this->can_send_event_from_php = (int) $this->can_send_event_from_php; 220 | return parent::beforeValidate(); 221 | } 222 | 223 | /** 224 | * @param Channel $channel 225 | * @param Subscriber $subscriber 226 | */ 227 | private static function _addToCache(Channel $channel, Subscriber $subscriber) { 228 | if (!array_key_exists($channel->id, self::$_channelSubscribers)) { 229 | self::$_channelSubscribers[$channel->id] = array(); 230 | } 231 | self::$_channelSubscribers[$channel->id][$subscriber->id] = $subscriber; 232 | if (!array_key_exists($subscriber->id, self::$_subscriberChannels)) { 233 | self::$_subscriberChannels[$subscriber->id] = array(); 234 | } 235 | self::$_channelSubscribers[$subscriber->id][$channel->id] = $channel; 236 | } 237 | } -------------------------------------------------------------------------------- /tests/php/components/db/BaseDriverTest.php: -------------------------------------------------------------------------------- 1 | user_id = 1; 14 | $this->assertEquals(true, $subscriber->save(), 'Can not save subscriber'); 15 | 16 | $channel = new Channel(); 17 | $channel->name = 'test'; 18 | $this->assertEquals(true, $channel->save(), 'Can not save channel'); 19 | } 20 | 21 | public function testDelete() { 22 | // TODO: Implement testDelete() method. 23 | } 24 | 25 | public function testRefresh() { 26 | // TODO: Implement testRefresh() method. 27 | } 28 | 29 | public function testFindByAttributes() { 30 | // TODO: Implement testFindByAttributes() method. 31 | } 32 | 33 | public function testFindAllByAttributes() { 34 | // TODO: Implement testFindAllByAttributes() method. 35 | } 36 | 37 | protected function tearDown() { 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /tests/php/frames/AFrameTest.php: -------------------------------------------------------------------------------- 1 | _nodeSocket = Yii::app()->nodeSocket; 23 | $this->_frame = $this->createFrame(); 24 | } 25 | 26 | abstract protected function getFrameClass(); 27 | 28 | /** 29 | * @return \YiiNodeSocket\Frame\AFrame 30 | */ 31 | protected function createFrame() { 32 | return Yii::app()->nodeSocket->{'create' . $this->getFrameClass() . 'Frame'}(); 33 | } 34 | 35 | public function testCreateEventFrameViaNodeSocketComponent() { 36 | $this->assertInstanceOf('YiiNodeSocket\Frame\\' . $this->getFrameClass() , Yii::app()->nodeSocket->{'create' . $this->getFrameClass() . 'Frame'}()); 37 | } 38 | 39 | public function testCreateEventFrameViaMultipleFrame() { 40 | $frame = Yii::app()->nodeSocket->createMultipleFrame(); 41 | $this->assertInstanceOf('YiiNodeSocket\Frame\Multiple', $frame); 42 | $this->assertInstanceOf('YiiNodeSocket\Frame\\' . $this->getFrameClass(), $frame->{'create' . $this->getFrameClass() . 'Frame'}()); 43 | } 44 | 45 | public function testMetaDataType() { 46 | $this->assertEquals(true, is_array($this->_frame->getMetaData())); 47 | } 48 | } -------------------------------------------------------------------------------- /tests/php/frames/EventTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(\YiiNodeSocket\Frame\AFrame::TYPE_EVENT, Yii::app()->nodeSocket->createEventFrame()->getType()); 18 | } 19 | 20 | public function testIsValid() { 21 | $frame = $this->createFrame(); 22 | $this->assertEquals(true, $frame->isValid()); // true because default event is undefined 23 | 24 | $frame->setEventName('test'); 25 | $this->assertEquals(true, $frame->isValid()); 26 | } 27 | 28 | /** 29 | * Test default event name (undefined) 30 | */ 31 | public function testDefaultEventName() { 32 | $metaData = $this->createFrame()->getMetaData(); 33 | $this->assertArrayHasKey('event', $metaData, 'Has no default event name: undefined'); 34 | $this->assertEquals('undefined', $metaData['event']); 35 | } 36 | 37 | /** 38 | * Test setting up event name 39 | */ 40 | public function testSetEventName() { 41 | $frame = $this->createFrame(); 42 | $frame->setEventName('test'); 43 | 44 | $meta = $frame->getMetaData(); 45 | 46 | $this->assertArrayHasKey('event', $meta, 'Has no event into metadata'); 47 | $this->assertEquals('test', $meta['event']); 48 | } 49 | 50 | /** 51 | * @param $name 52 | * 53 | * @dataProvider invalidEventNamesDataProvider 54 | */ 55 | public function testSetInvalidName($name) { 56 | $frame = $this->createFrame(); 57 | $meta = $frame->getMetaData(); 58 | $this->assertArrayHasKey('event', $meta, 'Has no event key into metadata'); 59 | $frame->setEventName($name); 60 | $meta = $frame->getMetaData(); 61 | $this->assertEquals('undefined', $meta['event']); 62 | } 63 | 64 | public function testSetValidRoomName() { 65 | $frame = $this->createFrame(); 66 | $frame->setRoom('test'); 67 | $data = $frame->getMetaData(); 68 | $this->assertArrayHasKey('room', $data, 'Frame meta data has no room key'); 69 | $this->assertEquals('test', $data['room']); 70 | } 71 | 72 | /** 73 | * @param $name 74 | * 75 | * @dataProvider invalidRoomNamesDataProvider 76 | */ 77 | public function testSetInvalidRoomName($name) { 78 | $frame = $this->createFrame(); 79 | $meta = $frame->getMetaData(); 80 | $this->assertArrayNotHasKey('room', $meta, 'Metadata has key room, but do not has it'); 81 | $frame->setRoom($name); 82 | $meta = $frame->getMetaData(); 83 | $this->assertArrayNotHasKey('room', $meta, 'Meta data has room'); 84 | } 85 | 86 | public function invalidEventNamesDataProvider() { 87 | return array( 88 | array(''), 89 | array(123), 90 | array(true), 91 | array(123.12), 92 | array(array()), 93 | array(array(1)), 94 | array(new stdClass()) 95 | ); 96 | } 97 | 98 | public function invalidRoomNamesDataProvider() { 99 | return array( 100 | array(''), 101 | array(true), 102 | array(array()), 103 | array(array(1)), 104 | array(new stdClass()) 105 | ); 106 | } 107 | } -------------------------------------------------------------------------------- /tests/php/frames/InvokeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(\YiiNodeSocket\Frame\AFrame::TYPE_INVOKE, Yii::app()->nodeSocket->createInvokeFrame()->getType()); 13 | } 14 | 15 | public function testGetFunctions() { 16 | $frame = $this->createFrame(); 17 | $this->assertEquals(array(), $frame->getFunctions(), 'Functions should be empty after construct'); 18 | } 19 | 20 | public function getGetMethods() { 21 | $frame = $this->createFrame(); 22 | $this->assertEquals(array(), $frame->getMethods(), 'Methods should be empty after construct'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/php/models/AModelTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('NodeSocket', $model->getNodeSocket()); 11 | } 12 | 13 | /** 14 | * @param \YiiNodeSocket\Models\AModel $model 15 | * 16 | * @dataProvider modelsDataProvider 17 | */ 18 | public function testGetDbDriver(\YiiNodeSocket\Models\AModel $model) { 19 | $this->assertInstanceOf('YiiNodeSocket\Components\Db\DriverInterface', $model->getDbDriver()); 20 | } 21 | 22 | public function modelsDataProvider() { 23 | return array( 24 | array(new \YiiNodeSocket\Models\Subscriber()), 25 | array(new \YiiNodeSocket\Models\Channel()), 26 | array(new \YiiNodeSocket\Models\SubscriberChannel()) 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/php/models/ChannelTest.php: -------------------------------------------------------------------------------- 1 | sid = microtime(); 13 | $this->assertEquals(true, $subscriber->save(), 'Can not save subscriber'); 14 | 15 | $channel = new Channel(); 16 | $channel->name = microtime(); 17 | $this->assertEquals(true, $channel->save()); 18 | 19 | $this->assertEmpty($channel->getSubscribers()); 20 | 21 | $this->assertEquals(true, $channel->subscribe($subscriber)); 22 | 23 | $subscribers = $channel->getSubscribers(); 24 | $this->assertNotEmpty($subscribers); 25 | $this->assertCount(1, $subscribers); 26 | $this->assertEquals(true, $subscriber->id == $subscribers[$subscriber->id]->id); 27 | 28 | $secondSubscriber = new Subscriber(); 29 | $secondSubscriber->sid = 'sdf0s9duf32' . time(); 30 | $this->assertEquals(true, $secondSubscriber->save()); 31 | 32 | $this->assertEquals(true, $channel->subscribe($secondSubscriber)); 33 | $subscribers = $channel->getSubscribers(); 34 | $this->assertCount(2, $subscribers); 35 | $this->assertEquals(true, $secondSubscriber->id == $subscribers[$secondSubscriber->id]->id); 36 | 37 | // load channel 38 | $channel = Channel::model()->findByPk($channel->id); 39 | $this->assertInstanceOf('YiiNodeSocket\Models\Channel', $channel); 40 | /** @var Channel $channel */ 41 | $subscribers = $channel->getSubscribers(); 42 | $this->assertNotEmpty($subscribers); 43 | $this->assertCount(2, $subscribers); 44 | 45 | return $channel; 46 | } 47 | 48 | /** 49 | * @param Channel $channel 50 | * 51 | * @depends testSubscribe 52 | */ 53 | public function testUnSubscriber(Channel $channel) { 54 | $subscribers = $channel->getSubscribers(); 55 | $this->assertCount(2, $subscribers); 56 | $subscriber = array_pop($subscribers); 57 | 58 | $this->assertTrue($channel->unSubscribe($subscriber)); 59 | $subscribers = $channel->getSubscribers(); 60 | $this->assertCount(1, $subscribers); 61 | $subscriber = array_pop($subscribers); 62 | 63 | $this->assertTrue($channel->unSubscribe($subscriber)); 64 | $subscribers = $channel->getSubscribers(); 65 | $this->assertCount(0, $subscribers); 66 | 67 | $this->assertCount(0, $channel->getSubscribers()); 68 | $this->assertTrue($channel->delete()); 69 | } 70 | 71 | /** 72 | * @dataProvider invalidDataProviderForTestSaveWithInvalidChannel 73 | */ 74 | public function testSaveWithInvalidChannel(Channel $channel) { 75 | // test is new record 76 | $this->assertEquals(true, $channel->getIsNewRecord()); 77 | 78 | // test save 79 | $this->assertEquals(false, $channel->save()); 80 | 81 | // has errors 82 | $this->assertEquals(true, $channel->hasErrors()); 83 | 84 | // test new record 85 | $this->assertEquals(true, $channel->getIsNewRecord()); 86 | } 87 | 88 | /** 89 | * @param Channel $channel 90 | * 91 | * @dataProvider validDataProviderForTestSave 92 | */ 93 | public function testSave(Channel $channel) { 94 | 95 | // test is new record 96 | $this->assertEquals(true, $channel->getIsNewRecord()); 97 | 98 | // test save 99 | $this->assertEquals(true, $channel->save()); 100 | 101 | // test new record 102 | $this->assertEquals(false, $channel->getIsNewRecord()); 103 | 104 | // repeat save 105 | $this->assertEquals(true, $channel->save()); 106 | 107 | // change name 108 | $name = $channel->name .= 'changed'; 109 | $this->assertEquals(true, $channel->save()); 110 | $this->assertEquals($name, $channel->name); 111 | 112 | // load by pk 113 | $_channel = Channel::model()->findByPk($channel->id); 114 | $this->assertInstanceOf('YiiNodeSocket\Models\Channel', $_channel); 115 | 116 | // test get is new record 117 | $this->assertEquals(false, $_channel->getIsNewRecord()); 118 | 119 | // test delete 120 | $this->assertEquals(true, $channel->delete()); 121 | } 122 | 123 | public function testDelete() { 124 | $channel = new Channel(); 125 | $channel->attributes = array( 126 | 'name' => 'test6' . time(), 127 | 'subscriber_source' => Channel::SOURCE_PHP, 128 | 'event_source' => Channel::SOURCE_PHP 129 | ); 130 | 131 | $this->assertEquals(true, $channel->getIsNewRecord()); 132 | $this->assertEquals(true, $channel->save()); 133 | 134 | // try find channel 135 | $channel = Channel::model()->findByPk($channel->id); 136 | $this->assertInstanceOf('YiiNodeSocket\Models\Channel', $channel); 137 | // try delete 138 | $this->assertEquals(true, $channel->delete()); 139 | 140 | // try find again 141 | $channel = Channel::model()->findByPk($channel->id); 142 | // should be null 143 | $this->assertNull($channel); 144 | } 145 | 146 | public function invalidDataProviderForTestSaveWithInvalidChannel() { 147 | $provider = array(); 148 | foreach ($this->getInValidChannelDataArray() as $attributes) { 149 | $channel = new Channel(); 150 | $channel->attributes = $attributes; 151 | $provider[] = array($channel); 152 | } 153 | return $provider; 154 | } 155 | 156 | public function validDataProviderForTestSave() { 157 | $provider = array(); 158 | foreach ($this->getValidChannelDataArray() as $attributes) { 159 | $attributes['name'] .= time(); 160 | $channel = new Channel(); 161 | $channel->attributes = $attributes; 162 | $provider[] = array($channel); 163 | } 164 | return $provider; 165 | } 166 | 167 | protected function getInValidChannelDataArray() { 168 | return array( 169 | array( 170 | 'name' => '', 171 | 'properties' => false 172 | ), 173 | array( 174 | 'name' => array(), 175 | 'properties' => 12.2 176 | ), 177 | array( 178 | 'name' => false, 179 | 'is_authentication_required' => '1dssds', 180 | 'properties' => function () { 181 | 182 | } 183 | ), 184 | array( 185 | 'name' => 'test3', 186 | 'subscriber_source' => 121 187 | ), 188 | array( 189 | 'name' => 'test6', 190 | 'subscriber_source' => array(), 191 | 'event_source' => 'sdfgsdf' 192 | ), 193 | array( 194 | 'is_authentication_required' => 2342, 195 | 'allowed_roles' => 23423 196 | ) 197 | ); 198 | } 199 | 200 | protected function getValidChannelDataArray() { 201 | return array( 202 | array( 203 | 'name' => 'test', 204 | 'properties' => '{}' 205 | ), 206 | array( 207 | 'name' => 'test1', 208 | 'is_authentication_required' => true, 209 | 'properties' => array( 210 | 'test' => 'value' 211 | ) 212 | ), 213 | array( 214 | 'name' => 'test2', 215 | 'is_authentication_required' => false, 216 | 'properties' => new stdClass() 217 | ), 218 | array( 219 | 'name' => 'test3', 220 | 'subscriber_source' => Channel::SOURCE_JAVASCRIPT 221 | ), 222 | array( 223 | 'name' => 'test4', 224 | 'subscriber_source' => Channel::SOURCE_PHP 225 | ), 226 | array( 227 | 'name' => 'test5', 228 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT 229 | ), 230 | array( 231 | 'name' => 'test6', 232 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 233 | 'event_source' => Channel::SOURCE_PHP 234 | ), 235 | array( 236 | 'name' => 'test7', 237 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 238 | 'event_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT 239 | ), 240 | array( 241 | 'name' => 'test8', 242 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 243 | 'event_source' => Channel::SOURCE_JAVASCRIPT 244 | ), 245 | array( 246 | 'name' => 'test9', 247 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 248 | 'event_source' => Channel::SOURCE_JAVASCRIPT, 249 | 'is_authentication_required' => true, 250 | 'allowed_roles' => 'user, moderator' 251 | ), 252 | array( 253 | 'name' => 'test10', 254 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 255 | 'event_source' => Channel::SOURCE_JAVASCRIPT, 256 | 'is_authentication_required' => true, 257 | 'allowed_roles' => array( 258 | 'user', 259 | 'moderator' 260 | ) 261 | ), 262 | array( 263 | 'name' => 'test11', 264 | 'subscriber_source' => Channel::SOURCE_PHP_OR_JAVASCRIPT, 265 | 'event_source' => Channel::SOURCE_JAVASCRIPT, 266 | 'is_authentication_required' => false, 267 | 'allowed_roles' => array( 268 | 'user', 269 | 'moderator' 270 | ) 271 | ) 272 | ); 273 | } 274 | } -------------------------------------------------------------------------------- /tests/php/models/SubscriberTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(true, $subscriber->getIsNewRecord()); 13 | 14 | $subscriber->user_id = 1; 15 | 16 | $this->assertEquals(true, $subscriber->save()); 17 | $this->assertEquals(false, $subscriber->getIsNewRecord()); 18 | 19 | $_subscriber = Subscriber::model()->findByPk($subscriber->id); 20 | $this->assertInstanceOf('YiiNodeSocket\Models\Subscriber', $_subscriber); 21 | $this->assertFalse($_subscriber->getIsNewRecord()); 22 | 23 | $this->assertEquals(true, $subscriber->delete(), 'Can not delete subscriber'); 24 | } 25 | 26 | /** 27 | * @param Subscriber $subscriber 28 | * 29 | * @return null|\YiiNodeSocket\Models\AModel 30 | * @dataProvider dataProviderForSave 31 | */ 32 | public function testSave(Subscriber $subscriber) { 33 | // save 34 | $this->assertEquals(true, $subscriber->save()); 35 | 36 | // check id 37 | $this->assertEquals(true, !empty($subscriber->id)); 38 | 39 | // update 40 | $this->assertEquals(true, $subscriber->save()); 41 | 42 | $subscriber->user_id = null; 43 | $subscriber->sid = 'sdfouo8u43u8dfuj34'; 44 | 45 | $this->assertEquals(true, $subscriber->save()); 46 | $this->assertEquals('sdfouo8u43u8dfuj34', $subscriber->sid); 47 | $this->assertNull($subscriber->user_id); 48 | 49 | $subscriber->sid = null; 50 | $subscriber->user_id = 121; 51 | 52 | $this->assertEquals(true, $subscriber->save()); 53 | $this->assertEquals(121, $subscriber->user_id); 54 | $this->assertNull($subscriber->sid); 55 | 56 | // find subscriber by id 57 | $subscriber = Subscriber::model()->findByPk($subscriber->id); 58 | $this->assertInstanceOf('YiiNodeSocket\Models\Subscriber', $subscriber); 59 | 60 | $this->assertEquals(true, $subscriber->delete()); 61 | 62 | return $subscriber; 63 | } 64 | 65 | public function testDelete() { 66 | $subscriber = new Subscriber(); 67 | $subscriber->sid = 'sd8uo329psd'; 68 | 69 | $this->assertEquals(true, $subscriber->save()); 70 | 71 | $subscriber = Subscriber::model()->findByPk($subscriber->id); 72 | 73 | $this->assertInstanceOf('YiiNodeSocket\Models\Subscriber', $subscriber); 74 | 75 | $this->assertEquals(true, $subscriber->delete()); 76 | 77 | $this->assertNull(Subscriber::model()->findByPk($subscriber->id), 'Subscriber need to be null because it has been deleted'); 78 | } 79 | 80 | public function dataProviderForSave() { 81 | $subscriber1 = new Subscriber(); 82 | $subscriber1->user_id = 1; 83 | 84 | $subscriber2 = new Subscriber(); 85 | $subscriber2->user_id = 1; 86 | $subscriber2->role = 'user'; 87 | $subscriber2->sid = 'fg9d8uo328uosdfgdf'; 88 | 89 | $subscriber3 = new Subscriber(); 90 | $subscriber3->role = 'moderator'; 91 | $subscriber3->sid = '9d8fu09ou439od'; 92 | 93 | $subscriber4 = new Subscriber(); 94 | $subscriber4->sid = 'fd98go4u3o94o3'; 95 | $subscriber4->sid_expiration = time() + 300; 96 | return array( 97 | array($subscriber1), 98 | array($subscriber2), 99 | array($subscriber3), 100 | array($subscriber4) 101 | ); 102 | } 103 | } --------------------------------------------------------------------------------