├── .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 | [](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 |
9 | - 1. Open "Go to this page for catch events example" page for catching events
10 | - 2. Open "Send simple event" or "Send simple room event" and see result on "Go to this page for catch events example"
11 |
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 | }
--------------------------------------------------------------------------------