Repository: NetEase/pomelo Branch: master Commit: e1615c130517 Files: 156 Total size: 565.2 KB Directory structure: gitextract_d3h0eibp/ ├── .gitignore ├── .jshintrc ├── .travis.yml ├── AUTHORS ├── History.md ├── LICENSE ├── README.md ├── bin/ │ └── pomelo ├── coverage/ │ └── blanket.js ├── gruntfile.js ├── index.js ├── lib/ │ ├── application.js │ ├── common/ │ │ ├── manager/ │ │ │ ├── appManager.js │ │ │ └── taskManager.js │ │ ├── remote/ │ │ │ ├── backend/ │ │ │ │ └── msgRemote.js │ │ │ └── frontend/ │ │ │ ├── channelRemote.js │ │ │ └── sessionRemote.js │ │ └── service/ │ │ ├── backendSessionService.js │ │ ├── channelService.js │ │ ├── connectionService.js │ │ ├── filterService.js │ │ ├── handlerService.js │ │ └── sessionService.js │ ├── components/ │ │ ├── backendSession.js │ │ ├── channel.js │ │ ├── connection.js │ │ ├── connector.js │ │ ├── dictionary.js │ │ ├── master.js │ │ ├── monitor.js │ │ ├── protobuf.js │ │ ├── proxy.js │ │ ├── pushScheduler.js │ │ ├── remote.js │ │ ├── server.js │ │ └── session.js │ ├── connectors/ │ │ ├── commands/ │ │ │ ├── handshake.js │ │ │ ├── heartbeat.js │ │ │ └── kick.js │ │ ├── common/ │ │ │ ├── coder.js │ │ │ └── handler.js │ │ ├── hybrid/ │ │ │ ├── switcher.js │ │ │ ├── tcpprocessor.js │ │ │ ├── tcpsocket.js │ │ │ └── wsprocessor.js │ │ ├── hybridconnector.js │ │ ├── hybridsocket.js │ │ ├── mqtt/ │ │ │ ├── generate.js │ │ │ ├── mqttadaptor.js │ │ │ └── protocol.js │ │ ├── mqttconnector.js │ │ ├── mqttsocket.js │ │ ├── sioconnector.js │ │ ├── siosocket.js │ │ ├── udpconnector.js │ │ └── udpsocket.js │ ├── filters/ │ │ ├── handler/ │ │ │ ├── serial.js │ │ │ ├── time.js │ │ │ ├── timeout.js │ │ │ └── toobusy.js │ │ └── rpc/ │ │ ├── rpcLog.js │ │ └── toobusy.js │ ├── index.js │ ├── master/ │ │ ├── master.js │ │ ├── starter.js │ │ └── watchdog.js │ ├── modules/ │ │ ├── console.js │ │ ├── masterwatcher.js │ │ └── monitorwatcher.js │ ├── monitor/ │ │ └── monitor.js │ ├── pomelo.js │ ├── pushSchedulers/ │ │ ├── buffer.js │ │ └── direct.js │ ├── server/ │ │ └── server.js │ └── util/ │ ├── appUtil.js │ ├── constants.js │ ├── countDownLatch.js │ ├── events.js │ ├── log.js │ ├── moduleUtil.js │ ├── pathUtil.js │ └── utils.js ├── package.json ├── template/ │ ├── game-server/ │ │ ├── app/ │ │ │ └── servers/ │ │ │ └── connector/ │ │ │ └── handler/ │ │ │ └── entryHandler.js │ │ ├── app.js │ │ ├── app.js.mqtt │ │ ├── app.js.sio │ │ ├── app.js.sio.wss │ │ ├── app.js.udp │ │ ├── app.js.wss │ │ ├── config/ │ │ │ ├── adminServer.json │ │ │ ├── adminUser.json │ │ │ ├── clientProtos.json │ │ │ ├── dictionary.json │ │ │ ├── log4js.json │ │ │ ├── master.json │ │ │ ├── serverProtos.json │ │ │ └── servers.json │ │ └── package.json │ ├── npm-install.bat │ ├── npm-install.sh │ ├── shared/ │ │ ├── server.crt │ │ └── server.key │ └── web-server/ │ ├── app.js │ ├── app.js.https │ ├── bin/ │ │ ├── component.bat │ │ └── component.sh │ ├── package.json │ └── public/ │ ├── css/ │ │ └── base.css │ ├── index.html │ ├── index.html.sio │ └── js/ │ └── lib/ │ ├── build/ │ │ ├── build.js │ │ └── build.js.wss │ ├── component.json │ ├── local/ │ │ └── boot/ │ │ ├── component.json │ │ └── index.js │ ├── pomeloclient.js │ ├── pomeloclient.js.wss │ └── socket.io.js └── test/ ├── application.js ├── config/ │ ├── log4js.json │ ├── master.json │ └── servers.json ├── filters/ │ ├── handler/ │ │ ├── serial.js │ │ ├── time.js │ │ ├── timeout.js │ │ └── toobusy.js │ └── rpc/ │ ├── rpcLog.js │ └── toobusy.js ├── logs/ │ └── tmp ├── manager/ │ ├── mockChannelManager.js │ └── taskManager.js ├── mock-base/ │ ├── .gitignore │ └── app/ │ ├── .gitignore │ └── servers/ │ ├── .file-start-with-dot │ ├── .folder-start-with-dot/ │ │ └── .gitignore │ ├── .gitignore │ ├── area/ │ │ └── .gitignore │ ├── connector/ │ │ ├── .gitignore │ │ ├── handler/ │ │ │ └── .gitignore │ │ └── remote/ │ │ └── .gitignore │ └── other-file ├── mock-plugin/ │ ├── components/ │ │ └── mockPlugin.js │ └── events/ │ └── mockEvent.js ├── modules/ │ └── console.js ├── pomelo.js ├── remote/ │ └── channelRemote.js ├── service/ │ ├── channel.js │ ├── channelService.js │ ├── connectionService.js │ ├── filterService.js │ ├── handlerService.js │ └── sessionService.js └── util/ ├── countDownLatch.js ├── pathUtil.js └── utils.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .project */node-log.log logs/*.log *.log !.gitignore node_modules/* node_modules_back/* .project .settings/ **/*.svn *.svn *.swp *.sublime-project *.sublime-workspace lib/doc/ lib-cov/ coverage.html .DS_Store .idea/* ================================================ FILE: .jshintrc ================================================ { "node": true, "camelcase": true, "eqeqeq": true, "undef": true, "unused": true, "curly": true, "newcap": true, "nonew": true, "trailing": true, "smarttabs": true, "noarg": true } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - "10.15.0" before_script: - npm install -g grunt-cli ================================================ FILE: AUTHORS ================================================ * Charlie Crane * Chang chang * piaohai * py8765 * Demon * numbcoder * halfblood * fantasyni ================================================ FILE: History.md ================================================ 2.2.7 / 2019-11-8 ================= * [#1150](https://github.com/NetEase/pomelo/pull/1150) 2.2.6 / 2019-7-10 ================= * [NEW] upgrade pomelo-admin to 1.0.1 2.2.5 / 2017-1-22 ================= * [#815](https://github.com/NetEase/pomelo/pull/815) * [#901](https://github.com/NetEase/pomelo/pull/901) 2.2.4 / 2017-1-20 ================= * [NEW] upgrade pomelo-rpc to 1.0.7 2.2.3 / 2017-1-20 ================= * [NEW] upgrade socket.io to 1.7.x 2.2.2 / 2017-1-20 ================= * [#899](https://github.com/NetEase/pomelo/pull/899) 2.2.1 / 2017-1-20 ================= * [FIX] fix push message with no array 2.2.0 / 2017-1-19 ================= * [NEW] upgrade pomelo-rpc to 1.0.6, rpc protocol moved to MQTT * [NEW] upgrade pomelo-admin to 1.0.0 * [NEW] pure javaScript without need to install c++ addons 1.2.1 / 2015-12-31 ================ * [NEW] upgrade ws to 0.8.0 * [#771](https://github.com/NetEase/pomelo/pull/771) * [#774](https://github.com/NetEase/pomelo/pull/774) * [FIX] tls: destory connection when clientError 1.2.0 / 2015-09-18 ================= * [NEW] upgrade pomelo-admin to 0.4.5 * [#751](https://github.com/NetEase/pomelo/pull/751) * [#741](https://github.com/NetEase/pomelo/pull/741) * [#740](https://github.com/NetEase/pomelo/pull/740) * [FIX] fix wrong variable in protobuf 1.1.9 / 2015-06-05 ================= * [NEW] upgrade pomelo-rpc to 0.4.10 * [NEW] upgrade pomelo-admin to 0.4.4 * [NEW] upgrade pomelo-logger to 0.1.7 1.1.8 / 2015-05-29 ================= * fix bug on verison calculating when update proto files * avoid modules loading error on windows * [NEW] upgrade pomelo-protocol to 0.1.6 1.1.7 / 2015-05-12 ================= * [#706](https://github.com/NetEase/pomelo/pull/706) * [#707](https://github.com/NetEase/pomelo/pull/707) * [#443](https://github.com/NetEase/pomelo/pull/443) * [#444](https://github.com/NetEase/pomelo/pull/444) * [#713](https://github.com/NetEase/pomelo/pull/713) * [NEW] upgrade pomelo-rpc to 0.4.9 * [NEW] upgrade pomelo-admin to 0.4.3 1.1.6 / 2015-03-12 ================= * [NEW] add configure file automatically reload feature * [NEW] add mqtt connector heartbeat timeout option * [NEW] upgrade pomelo-rpc to 0.4.8 1.1.5 / 2015-02-26 ================= * [NEW] upgrade pomelo-rpc to 0.4.7 1.1.4 / 2015-01-23 ================= [#670](https://github.com/NetEase/pomelo/pull/670) [#669](https://github.com/NetEase/pomelo/pull/669) [#666](https://github.com/NetEase/pomelo/pull/666) [#665](https://github.com/NetEase/pomelo/pull/665) [#662](https://github.com/NetEase/pomelo/pull/662) [#659](https://github.com/NetEase/pomelo/pull/659) [#657](https://github.com/NetEase/pomelo/pull/657) [#653](https://github.com/NetEase/pomelo/pull/653) 1.1.2 / 2014-11-12 ================= * [NEW] introduce updateUserInfo for connectionService [#637](https://github.com/NetEase/pomelo/pull/637) * [FIX] fix wrong variable err using [#642](https://github.com/NetEase/pomelo/pull/642) * [NEW] introduce cancelShutdownHook [#644](https://github.com/NetEase/pomelo/pull/644) * [FIX] revert PR #613, which should not be accepted [#649](https://github.com/NetEase/pomelo/pull/649) 1.1.1 / 2014-10-10 ================= * [NEW] upgrade pomelo-protocol to 0.1.4 [#616](https://github.com/NetEase/pomelo/pull/616) * [FIX] incorrect this scope [#622](https://github.com/NetEase/pomelo/pull/622) * [FIX] fix bug on arg parse [#623](https://github.com/NetEase/pomelo/pull/623) * [FIX] connection without communication bug 1.1.0 / 2014-09-12 ================= * [NEW] fit for libpomelo2 * [NEW] upgrade pomelo-rpc to 0.4.5 [#612](https://github.com/NetEase/pomelo/pull/612) * [FIX] close http server after WebSocketServer.close [#613](https://github.com/NetEase/pomelo/pull/613) * [FIX] update timeout.js [#614](https://github.com/NetEase/pomelo/pull/614) * [FIX] fix typo 1.0.4 / 2014-08-26 ================= * [NEW] upgrade pomelo-rpc to 0.4.3 * [NEW] upgrade pomelo-logger to 0.1.6 * [FIX] pomelo-masterha-plugin reconnect bug miss parameter env * [#582](https://github.com/NetEase/pomelo/pull/582) 1.0.3 / 2014-07-18 ================= * [NEW] dictVersion : Similar to `protoVersion`, add `dictVersion` and skip sending dict when handshaking if possible. [#572](https://github.com/NetEase/pomelo/pull/572) * [FIX] CRON : Upgrade pomelo-scheduler to v0.3.9. Fix a bug that will loss tasks if The number of days next month is greater than this month. [#560](https://github.com/NetEase/pomelo/pull/560) [pomelo-scheduler#4](https://github.com/NetEase/pomelo-scheduler/pull/4) * [FIX] hot update : Remove the error logging if a server doesn't have any handler. [#562](https://github.com/NetEase/pomelo/pull/562) * [NEW] protobuf : Add `protobuf cache` into libpomelo, and add `useProto` handshake option to tell the client whether to use the protobuf. [#564](https://github.com/NetEase/pomelo/pull/564) [libpomelo#58](https://github.com/NetEase/libpomelo/pull/58) [discuss(chinese)](http://nodejs.netease.com/topic/53c6c126898634292c8157a2) 1.0.2 / 2014-07-10 ================= * fix server reconnect bug 1.0.1 / 2014-07-03 ================= * merge pull request #538 #541 #545 #546 #547 * update master watchdog notify method * upgrade pomelo-rpc to 0.4.2 1.0.0 / 2014-06-19 ================= * mqtt connector * support ie6,7,8 with sioconnector * support hot update partially 1.0.0-pre / 2014-05-16 ================= * add udpconnector * pomelo-rpc load balancing and fault tolerance * connector wss & tls support * pomelo-zookeeper-plugin * pomelo-scale-plugin * environment directory configuration support * pomelo-cli dynamic script 0.9.10 / 2014-05-15 ================= * merge pull request #505 * merge pull request #506 * merge pull request from kaisatec * add getClientAddressBySessionId in sessionService 0.9.9 / 2014-05-06 ================= * merge pull request #495 * merge pull request #499 * merge pull request #501 * upgrade pomelo-admin to 0.3.4 0.9.8 / 2014-05-04 ================= * remove unused module * update constants definition 0.9.7 / 2014-04-25 ================= * merge pull request #486 * add channelservice rpc error info 0.9.6 / 2014-04-16 ================= * merge pull request #472 * merge pull request #475 * merge pull request #485 * update ssh config 0.9.5 / 2014-04-02 ================= * add support for different env * add session count method in sessionService * emit start_all_event * better prompt for init project 0.9.4 / 2014-03-20 ================= * merge pull request from zhaohaojie * merge pull request from wuxian * merge pull request from roytan883 * fix history.md year bug 0.9.3 / 2014-03-11 ================= * upgrade pomelo-admin to 0.3.2 for npm reason 0.9.2 / 2014-03-10 ================= * merge pull request sshPort * merge pull request #438 * fix hybridconnector dict bug * upgrade pomelo-rpc to 0.3.2 0.9.1 / 2014-03-03 ================= * fix pomelo stop auto-restart bug * add restart-force option * add application.require method * export constants.js 0.9.0 / 2014-02-26 ================= * rpc support for zmq * rpc requests callback timeout * rpc support for hot restart * optimize for command line * support for connection blacklist * protobuf support for decodeIO-protobuf.js * channel serialization interface 0.8.9 / 2014-02-21 ================= * fix fin_wait2 caused by socket.end bug 0.8.8 / 2014-02-19 ================= * fix some typos in comment 0.8.7 / 2014-01-28 ================= * refactor pomelo command, report remained servers if kill failed 0.8.6 / 2014-01-22 ================= * upgrade pomelo-rpc 0.2.9 * upgrade pomelo-admin 0.2.9 0.8.5 / 2014-01-22 ================= * upgrade pomelo-rpc 0.2.8 * upgrade pomelo-scheduler 0.3.8 0.8.4 / 2014-01-20 ================= * fix bin/pomelo spell bug 0.8.3 / 2014-01-16 ================= * add tcp socket close option * upgrade pomelo-rpc 0.2.7 * upgrade pomelo-admin 0.2.8 * upgrade pomelo-schedule 0.3.7 0.8.2 / 2014-01-03 ================= * fix session kick bug issue #355 * fix add rpc filter bug 0.8.1 / 2013-12-31 ================= * upgrade pomelo-rpc to 0.2.6 * handle rpc filter error * add test cases 0.8.0 / 2013-12-24 ================= * refactor bin/pomelo * pushScheduler add option * add rpc invoke method * lifecycle callback feature * add rcp filter interface * simplify servers.json configuration * pomelo-logger dynamic log level * pomelo-rpc & pomelo-admin white list * pomelo-data-plugin 0.7.7 / 2013-12-16 ================= * upgrade pomelo-loader to 0.0.6 * upgrade pomelo-logger to 0.1.2(add dynamic change logger level feature) 0.7.6 / 2013-12-3 ================= * upgrade pomelo-rpc to 0.2.4 * upgrade pomelo-admin to 0.2.6(fix reconnect bug) 0.7.5 / 2013-11-27 ================= * fix pomelo kill bug * fix rpc toobusy filter bug 0.7.4 / 2013-11-20 ================= * fix pomelo add command * master start servers in 2 mode, detached in production, no detched in development 0.7.3 / 2013-11-15 ================= * add heartbeat timeout option 0.7.2 / 2013-11-14 ================= * add start server detached mode * add masterha for pomelo stop&list * fix auto-restart disconnect bug * update pomelo start for different envs 0.7.1 / 2013-11-11 ================= * fix errorHandler bug * compatible for schedulerConfig 0.7.0 / 2013-11-6 ================= * crontab * global filter * transaction * pomelo-cli auto-complete * some components rename 0.6.8 / 2013-11-4 ================= * update pomelo-admin version 0.6.7 / 2013-10-14 ================= * fix masterha monitor reconnect bug 0.6.6 / 2013-10-12 ================= * merge pull request #303 replace tab & remove session get value argument * upgrade pomelo-admin to 0.2.4 * upgrade pomelo-monitor to 0.3.7 * upgrade pomelo-rpc to 0.2.2 0.6.5 / 2013-9-30 ================= * fix server reconnect bug * upgrade pomelo-admin to 0.2.3 0.6.4 / 2013-9-27 ================= * update logger config && test log4js config * update require pomelo path & unuse module * merge pull request update readme #295 0.6.3 / 2013-9-10 ================= * fix tcp socket package bug * update filter parameters * merge pull request localSession unbind #289 0.6.2 / 2013-9-5 ================= * upgrade pomelo-admin to 0.2.2 * update test cases * fix socket.on end bug 0.6.1 / 2013-9-2 ================= * update pomelo-admin & pomelo-rpc to 0.2.1 * add rpcDebug module in master 0.6.0 / 2013-8-26 ================= * interactive command line tool * plugin mechanism * data signature * handle invalid connections * rpc debug log * overload protection * servers reconnect mechanism * daemon start mode * packages upgrade 0.5.5 / 2013-8-9 ================= * fix sioconnector bug * fix localSession bug * merge pull request 0.5.4 / 2013-7-25 ================= * update pomelo-protocol version 0.5.3 / 2013-7-25 ================= * update check forever method * update socket.io transport * remove redis dependency for test cases 0.5.2 / 2013-7-23 ================= * fix hybridsocket send message bug * fix globalChannel nextTick bug * add some test cases 0.5.1 / 2013-7-19 ================= * update pomelo-protobuf version * receive servers console data event in production environment 0.5.0 / 2013-7-16 ================= * high availability for master(with zookeeper) * support global channel(with redis) * server bind to CPU * server auto-restart when server does not work(configurable) * add beforeStop hook for application 0.4.6 / 2013-7-15 ================= * fix pomelo-protocol bug, which will lose message when requestId is 128 multiple 0.4.5 / 2013-7-3 ================= * fix load scheduler component bug * fix hybridconnector check useDict bug * add keywords, issues, contributor infos to npm 0.4.3 / 2013-6-13 ================== * fix client heartbeat timeout bug * fix command line debug argument bug 0.4.2 / 2013-6-5 ================== * fix duplicated bind session bug * add `disconnectOnTimeout` option for hybridconnector * fix empty group push bug in channel * fix protobuf encode bug 0.4.1 / 2013-5-28 ================== * refactor protocol layers * support multiple sessions of the same user 0.3.10 / 2013-5-20 ================== * `pomelo-protocol` upgrades to 0.3.4 * fix session bind bug in backend server * replace `childprocess.exec` with `spawn` in `starter.js` * fix configure bug 0.3.9 / 2013-5-8 ================== * fix configure bug 0.3.8 / 2013-5-6 ================== * fix tcpsocket close event bug * fix error handler bug 0.3.7 / 2013-4-16 ================== * update templates * sioconnector supports flashsocket * add `distinctHost` to hybridconnector * fix rpc `cacheMsg` configure bugs 0.3.6 / 2013-4-9 ================== * compatible with node 0.10 version * fix daemon forever bugs * add some unit test case 0.3.5 / 2013-3-25 ================== * fix log4js not compatible bug * fix function redefined in localSessionServie 0.3.4 / 2013-3-19 ================== * fix server not verifing useDict, useProtobuf bug * fix can not start pomelo from ide bug * add host param in listen for hybridconnector, which is important for some load balance strategy 0.3.3 / 2013-3-12 ================== * fix double string decode bug when not compressing route 0.3.2 / 2013-3-11 ================== * fix init template bug * modify command line help, version to --help, --version 0.3.1 / 2013-3-7 ================== * add hybridconnector to support socket and websocket * add route dictionary and protobuf for binary protocol * add localSession query interfaces * add broadcast method for ChannelService 0.2.5 / 2013-2-28 ================== * dynamic add and remove servers (watchdog module) * fix filterService before filter bug * fix connector component bug 0.2.4 / 2013-1-4 ================== * fix stop components bug * add windows install .bat * add comand line windows compatible feature 0.2.3 / 2012-12-25 ================== * add mkdirp, update pomelo-admin version * solve windows comptaible problem 0.2.2 / 2012-12-9 ================== * add fail ids for channel push method * code format standardize 0.2.0 / 2012-11-20 ================== * establish project on github 0.1.x / before 2012-11 ================== * internal development for 11 months ================================================ FILE: LICENSE ================================================ (The MIT License) Copyright (c) 2012-2014 Netease, Inc. and other pomelo contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ## Pomelo -- a fast, scalable game server framework for node.js Pomelo is a fast, scalable game server framework for [node.js](http://nodejs.org). It provides the basic development framework and many related components, including libraries and tools. Pomelo is also suitable for real-time web applications; its distributed architecture makes pomelo scale better than other real-time web frameworks. [![Build Status](https://travis-ci.org/NetEase/pomelo.svg?branch=master)](https://travis-ci.org/NetEase/pomelo) * Homepage: * Mailing list: * Documentation: * Wiki: * Issues: * Tags: game, nodejs ## Features ### Complete support of game server and realtime application server architecture * Multiple-player game: mobile, social, web, MMO rpg(middle size) * Realtime application: chat, message push, etc. ### Fast, scalable * Distributed (multi-process) architecture, can be easily scale up * Flexible server extension * Full performance optimization and test ### Easy * Simple API: request, response, broadcast, etc. * Lightweight: high development efficiency based on node.js * Convention over configuration: almost zero config ### Powerful * Many clients support, including javascript, flash, android, iOS, cocos2d-x, C * Many libraries and tools, including command line tool, admin tool, performance test tool, AI, path finding etc. * Good reference materials: full docs, many examples and [an open-source MMO RPG demo](https://github.com/NetEase/pomelo/wiki/Introduction-to--Lord-of-Pomelo) ### Extensible * Support plugin architecture, easy to add new features through plugins. We also provide many plugins like online status, master high availability. * Custom features, users can define their own network protocol, custom components very easy. ## Why should I use pomelo? Fast, scalable, real-time game server development is not an easy job, and a good container or framework can reduce its complexity. Unfortunately, unlike web, finding a game server framework solution is difficult, especially an open source solution. Pomelo fills this gap, providing a full solution for building game server frameworks. Pomelo has the following advantages: * The architecture is scalable. It uses a multi-process, single thread runtime architecture, which has been proven in the industry and is especially suited to the node.js thread model. * Easy to use, the development model is quite similar to web, using convention over configuration, with almost zero config. The [API](http://pomelo.netease.com/api.html) is also easy to use. * The framework is extensible. Based on the node.js micro module principle, the core of pomelo is small. All of the components, libraries and tools are individual npm modules, and anyone can create their own module to extend the framework. * The reference materials and documentation are quite complete. In addition to the documentation, we also provide [an open-source MMO RPG demo](https://github.com/NetEase/pomelo/wiki/Introduction-to--Lord-of-Pomelo) (HTML5 client), which is a far better reference material than any book. ## How can I develop with pomelo? With the following references, you can quickly familiarize yourself with the pomelo development process: * [Pomelo documents](https://github.com/NetEase/pomelo/wiki) * [Getting started](https://github.com/NetEase/pomelo/wiki/Welcome-to-Pomelo) * [Tutorial](https://github.com/NetEase/pomelo/wiki/Preface) ## Contributors * NetEase, Inc. (@NetEase) * Peter Johnson(@missinglink) * Aaron Yoshitake * @D-Deo * Eduard Gotwig * Eric Muyser(@stokegames) * @GeforceLee * Harold Jiang(@jzsues) * @ETiV * [kaisatec](https://github.com/kaisatec) * [roytan883](https://github.com/roytan883) * [wuxian](https://github.com/wuxian) * [zxc122333](https://github.com/zxc122333) * [newebug](https://github.com/newebug) * [jiangzhuo](https://github.com/jiangzhuo) * [youxiachai](https://github.com/youxiachai) * [qiankanglai](https://github.com/qiankanglai) * [xieren58](https://github.com/xieren58) * [prim](https://github.com/prim) * [Akaleth](https://github.com/Akaleth) * [pipi32167](https://github.com/pipi32167) * [ljhsai](https://github.com/ljhsai) * [zhanghaojie](https://github.com/zhanghaojie) * [airandfingers](https://github.com/airandfingers) ## License (The MIT License) Copyright (c) 2012-2017 NetEase, Inc. and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: bin/pomelo ================================================ #!/usr/bin/env node /** * Module dependencies. */ var fs = require('fs'), os = require('os'), path = require('path'), util = require('util'), cliff = require('cliff'), mkdirp = require('mkdirp'), co = require('../lib/modules/console'), utils = require('../lib/util/utils'), starter = require('../lib/master/starter'), exec = require('child_process').exec, spawn = require('child_process').spawn, version = require('../package.json').version, adminClient = require('pomelo-admin').adminClient, constants = require('../lib/util/constants'), program = require('commander'); /** * Constant Variables */ var TIME_INIT = 1 * 1000; var TIME_KILL_WAIT = 5 * 1000; var KILL_CMD_LUX = 'kill -9 `ps -ef|grep node|awk \'{print $2}\'`'; var KILL_CMD_WIN = 'taskkill /im node.exe /f'; var CUR_DIR = process.cwd(); var DEFAULT_GAME_SERVER_DIR = CUR_DIR; var DEFAULT_USERNAME = 'admin'; var DEFAULT_PWD = 'admin'; var DEFAULT_ENV = 'development'; var DEFAULT_MASTER_HOST = '127.0.0.1'; var DEFAULT_MASTER_PORT = 3005; var CONNECT_ERROR = 'Fail to connect to admin console server.'; var FILEREAD_ERROR = 'Fail to read the file, please check if the application is started legally.'; var CLOSEAPP_INFO = 'Closing the application......\nPlease wait......'; var ADD_SERVER_INFO = 'Successfully add server.'; var RESTART_SERVER_INFO = 'Successfully restart server.'; var INIT_PROJ_NOTICE = '\nThe default admin user is: \n\n'+ ' username'.green + ': admin\n ' + 'password'.green+ ': admin\n\nYou can configure admin users by editing adminUser.json later.\n '; var SCRIPT_NOT_FOUND = 'Fail to find an appropriate script to run,\nplease check the current work directory or the directory specified by option `--directory`.\n'.red; var MASTER_HA_NOT_FOUND = 'Fail to find an appropriate masterha config file, \nplease check the current work directory or the arguments passed to.\n'.red; var COMMAND_ERROR = 'Illegal command format. Use `pomelo --help` to get more info.\n'.red; var DAEMON_INFO = 'The application is running in the background now.\n'; program.version(version); program.command('init [path]') .description('create a new application') .action(function(path) { init(path || CUR_DIR); }); program.command('start') .description('start the application') .option('-e, --env ', 'the used environment', DEFAULT_ENV) .option('-D, --daemon', 'enable the daemon start') .option('-d, --directory, ', 'the code directory', DEFAULT_GAME_SERVER_DIR) .option('-t, --type ,', 'start server type') .option('-i, --id ', 'start server id') .action(function(opts) { start(opts); }); program.command('list') .description('list the servers') .option('-u, --username ', 'administration user name', DEFAULT_USERNAME) .option('-p, --password ', 'administration password', DEFAULT_PWD) .option('-h, --host ', 'master server host', DEFAULT_MASTER_HOST) .option('-P, --port ', 'master server port', DEFAULT_MASTER_PORT) .action(function(opts) { list(opts); }); program.command('add') .description('add a new server') .option('-u, --username ', 'administration user name', DEFAULT_USERNAME) .option('-p, --password ', 'administration password', DEFAULT_PWD) .option('-h, --host ', 'master server host', DEFAULT_MASTER_HOST) .option('-P, --port ', 'master server port', DEFAULT_MASTER_PORT) .action(function() { var args = [].slice.call(arguments, 0); var opts = args[args.length - 1]; opts.args = args.slice(0, -1); add(opts); }); program.command('stop') .description('stop the servers, for multiple servers, use `pomelo stop server-id-1 server-id-2`') .option('-u, --username ', 'administration user name', DEFAULT_USERNAME) .option('-p, --password ', 'administration password', DEFAULT_PWD) .option('-h, --host ', 'master server host', DEFAULT_MASTER_HOST) .option('-P, --port ', 'master server port', DEFAULT_MASTER_PORT) .action(function() { var args = [].slice.call(arguments, 0); var opts = args[args.length - 1]; opts.serverIds = args.slice(0, -1); terminal('stop', opts); }); program.command('kill') .description('kill the application') .option('-u, --username ', 'administration user name', DEFAULT_USERNAME) .option('-p, --password ', 'administration password', DEFAULT_PWD) .option('-h, --host ', 'master server host', DEFAULT_MASTER_HOST) .option('-P, --port ', 'master server port', DEFAULT_MASTER_PORT) .option('-f, --force', 'using this option would kill all the node processes') .action(function() { var args = [].slice.call(arguments, 0); var opts = args[args.length - 1]; opts.serverIds = args.slice(0, -1); terminal('kill', opts); }); program.command('restart') .description('restart the servers, for multiple servers, use `pomelo restart server-id-1 server-id-2`') .option('-u, --username ', 'administration user name', DEFAULT_USERNAME) .option('-p, --password ', 'administration password', DEFAULT_PWD) .option('-h, --host ', 'master server host', DEFAULT_MASTER_HOST) .option('-P, --port ', 'master server port', DEFAULT_MASTER_PORT) .option('-t, --type ,', 'start server type') .option('-i, --id ', 'start server id') .action(function(opts) { restart(opts); }); program.command('masterha') .description('start all the slaves of the master') .option('-d, --directory ', 'the code directory', DEFAULT_GAME_SERVER_DIR) .action(function(opts) { startMasterha(opts); }); program.command('*') .action(function() { console.log(COMMAND_ERROR); }); program.parse(process.argv); /** * Init application at the given directory `path`. * * @param {String} path */ function init(path) { console.log(INIT_PROJ_NOTICE); connectorType(function(type) { emptyDirectory(path, function(empty) { if(empty) { process.stdin.destroy(); createApplicationAt(path, type); } else { confirm('Destination is not empty, continue? (y/n) [no] ', function(force) { process.stdin.destroy(); if(force) { createApplicationAt(path, type); } else { abort('Fail to init a project'.red); } }); } }); }); } /** * Create directory and files at the given directory `path`. * * @param {String} ph */ function createApplicationAt(ph, type) { var name = path.basename(path.resolve(CUR_DIR, ph)); copy(path.join(__dirname, '../template/'), ph); mkdir(path.join(ph, 'game-server/logs')); mkdir(path.join(ph, 'shared')); // rmdir -r var rmdir = function(dir) { var list = fs.readdirSync(dir); for(var i = 0; i < list.length; i++) { var filename = path.join(dir, list[i]); var stat = fs.statSync(filename); if(filename === "." || filename === "..") { } else if(stat.isDirectory()) { rmdir(filename); } else { fs.unlinkSync(filename); } } fs.rmdirSync(dir); }; setTimeout(function() { switch(type) { case '1': // use websocket var unlinkFiles = ['game-server/app.js.sio', 'game-server/app.js.wss', 'game-server/app.js.mqtt', 'game-server/app.js.sio.wss', 'game-server/app.js.udp', 'web-server/app.js.https', 'web-server/public/index.html.sio', 'web-server/public/js/lib/pomeloclient.js', 'web-server/public/js/lib/pomeloclient.js.wss', 'web-server/public/js/lib/build/build.js.wss', 'web-server/public/js/lib/socket.io.js']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } break; case '2': // use socket.io var unlinkFiles = ['game-server/app.js', 'game-server/app.js.wss', 'game-server/app.js.udp', 'game-server/app.js.mqtt', 'game-server/app.js.sio.wss', 'web-server/app.js.https', 'web-server/public/index.html', 'web-server/public/js/lib/component.json', 'web-server/public/js/lib/pomeloclient.js.wss']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } fs.renameSync(path.resolve(ph, 'game-server/app.js.sio'), path.resolve(ph, 'game-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html')); rmdir(path.resolve(ph, 'web-server/public/js/lib/build')); rmdir(path.resolve(ph, 'web-server/public/js/lib/local')); break; case '3': // use websocket wss var unlinkFiles = ['game-server/app.js.sio', 'game-server/app.js', 'game-server/app.js.udp', 'game-server/app.js.sio.wss', 'game-server/app.js.mqtt', 'web-server/app.js', 'web-server/public/index.html.sio', 'web-server/public/js/lib/pomeloclient.js', 'web-server/public/js/lib/pomeloclient.js.wss', 'web-server/public/js/lib/build/build.js', 'web-server/public/js/lib/socket.io.js']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } fs.renameSync(path.resolve(ph, 'game-server/app.js.wss'), path.resolve(ph, 'game-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/app.js.https'), path.resolve(ph, 'web-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/public/js/lib/build/build.js.wss'), path.resolve(ph, 'web-server/public/js/lib/build/build.js')); break; case '4': // use socket.io wss var unlinkFiles = ['game-server/app.js.sio', 'game-server/app.js', 'game-server/app.js.udp', 'game-server/app.js.wss', 'game-server/app.js.mqtt', 'web-server/app.js', 'web-server/public/index.html', 'web-server/public/js/lib/pomeloclient.js']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } fs.renameSync(path.resolve(ph, 'game-server/app.js.sio.wss'), path.resolve(ph, 'game-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/app.js.https'), path.resolve(ph, 'web-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html')); fs.renameSync(path.resolve(ph, 'web-server/public/js/lib/pomeloclient.js.wss'), path.resolve(ph, 'web-server/public/js/lib/pomeloclient.js')); rmdir(path.resolve(ph, 'web-server/public/js/lib/build')); rmdir(path.resolve(ph, 'web-server/public/js/lib/local')); fs.unlinkSync(path.resolve(ph, 'web-server/public/js/lib/component.json')); break; case '5': // use socket.io wss var unlinkFiles = ['game-server/app.js.sio', 'game-server/app.js', 'game-server/app.js.wss', 'game-server/app.js.mqtt', 'game-server/app.js.sio.wss', 'web-server/app.js.https', 'web-server/public/index.html', 'web-server/public/js/lib/component.json', 'web-server/public/js/lib/pomeloclient.js.wss']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } fs.renameSync(path.resolve(ph, 'game-server/app.js.udp'), path.resolve(ph, 'game-server/app.js')); rmdir(path.resolve(ph, 'web-server/public/js/lib/build')); rmdir(path.resolve(ph, 'web-server/public/js/lib/local')); break; case '6': // use socket.io var unlinkFiles = ['game-server/app.js', 'game-server/app.js.wss', 'game-server/app.js.udp', 'game-server/app.js.sio', 'game-server/app.js.sio.wss', 'web-server/app.js.https', 'web-server/public/index.html', 'web-server/public/js/lib/component.json', 'web-server/public/js/lib/pomeloclient.js.wss']; for(var i = 0; i < unlinkFiles.length; ++i) { fs.unlinkSync(path.resolve(ph, unlinkFiles[i])); } fs.renameSync(path.resolve(ph, 'game-server/app.js.mqtt'), path.resolve(ph, 'game-server/app.js')); fs.renameSync(path.resolve(ph, 'web-server/public/index.html.sio'), path.resolve(ph, 'web-server/public/index.html')); rmdir(path.resolve(ph, 'web-server/public/js/lib/build')); rmdir(path.resolve(ph, 'web-server/public/js/lib/local')); break; } var replaceFiles = ['game-server/app.js', 'game-server/package.json', 'web-server/package.json']; for(var j = 0; j < replaceFiles.length; j++) { var str = fs.readFileSync(path.resolve(ph, replaceFiles[j])).toString(); fs.writeFileSync(path.resolve(ph, replaceFiles[j]), str.replace('$', name)); } var f = path.resolve(ph, 'game-server/package.json'); var content = fs.readFileSync(f).toString(); fs.writeFileSync(f, content.replace('#', version)); }, TIME_INIT); } /** * Start application. * * @param {Object} opts options for `start` operation */ function start(opts) { var absScript = path.resolve(opts.directory, 'app.js'); if (!fs.existsSync(absScript)) { abort(SCRIPT_NOT_FOUND); } var logDir = path.resolve(opts.directory, 'logs'); if (!fs.existsSync(logDir)) { mkdir(logDir); } var ls; var type = opts.type || constants.RESERVED.ALL; var params = [absScript, 'env=' + opts.env, 'type=' + type]; if(!!opts.id) { params.push('startId=' + opts.id); } if (opts.daemon) { ls = spawn(process.execPath, params, {detached: true, stdio: 'ignore'}); ls.unref(); console.log(DAEMON_INFO); process.exit(0); } else { ls = spawn(process.execPath, params); ls.stdout.on('data', function(data) { console.log(data.toString()); }); ls.stderr.on('data', function(data) { console.log(data.toString()); }); } } /** * List pomelo processes. * * @param {Object} opts options for `list` operation */ function list(opts) { var id = 'pomelo_list_' + Date.now(); connectToMaster(id, opts, function(client) { client.request(co.moduleId, {signal: 'list'}, function(err, data) { if(err) { console.error(err); } var servers = []; for(var key in data.msg) { servers.push(data.msg[key]); } var comparer = function(a, b) { if (a.serverType < b.serverType) { return -1; } else if (a.serverType > b.serverType) { return 1; } else if (a.serverId < b.serverId) { return -1; } else if (a.serverId > b.serverId) { return 1; } else { return 0; } }; servers.sort(comparer); var rows = []; rows.push(['serverId', 'serverType', 'pid', 'rss(M)', 'heapTotal(M)', 'heapUsed(M)', 'uptime(m)']); servers.forEach(function(server) { rows.push([server.serverId, server.serverType, server.pid, server.rss, server.heapTotal, server.heapUsed, server.uptime]); }); console.log(cliff.stringifyRows(rows, ['red', 'blue', 'green', 'cyan', 'magenta', 'white', 'yellow'])); process.exit(0); }); }); } /** * Add server to application. * * @param {Object} opts options for `add` operation */ function add(opts) { var id = 'pomelo_add_' + Date.now(); connectToMaster(id, opts, function(client) { client.request(co.moduleId, { signal: 'add', args: opts.args }, function(err) { if(err) { console.error(err); } else { console.info(ADD_SERVER_INFO); } process.exit(0); }); }); } /** * Terminal application. * * @param {String} signal stop/kill * @param {Object} opts options for `stop/kill` operation */ function terminal(signal, opts) { console.info(CLOSEAPP_INFO); // option force just for `kill` if(opts.force) { if (os.platform() === constants.PLATFORM.WIN) { exec(KILL_CMD_WIN); } else { exec(KILL_CMD_LUX); } process.exit(1); return; } var id = 'pomelo_terminal_' + Date.now(); connectToMaster(id, opts, function(client) { client.request(co.moduleId, { signal: signal, ids: opts.serverIds }, function(err, msg) { if(err) { console.error(err); } if(signal === 'kill') { if(msg.code === 'ok') { console.log('All the servers have been terminated!'); } else { console.log('There may be some servers remained:', msg.serverIds); } } process.exit(0); }); }); } function restart(opts) { var id = 'pomelo_restart_' + Date.now(); var serverIds = []; var type = null; if(!!opts.id) { serverIds.push(opts.id); } if(!!opts.type) { type = opts.type; } connectToMaster(id, opts, function(client) { client.request(co.moduleId, { signal: 'restart', ids: serverIds, type: type}, function(err, fails) { if(!!err) { console.error(err); } else if(!!fails.length) { console.info('restart fails server ids: %j', fails); } else { console.info(RESTART_SERVER_INFO); } process.exit(0); }); }); } function connectToMaster(id, opts, cb) { var client = new adminClient({username: opts.username, password: opts.password, md5: true}); client.connect(id, opts.host, opts.port, function(err) { if(err) { abort(CONNECT_ERROR + err.red); } if(typeof cb === 'function') { cb(client); } }); } /** * Start master slaves. * * @param {String} option for `startMasterha` operation */ function startMasterha(opts) { var configFile = path.join(opts.directory, constants.FILEPATH.MASTER_HA); if(!fs.existsSync(configFile)) { abort(MASTER_HA_NOT_FOUND); } var masterha = require(configFile).masterha; for(var i=0; i * MIT Licensed */ /** * Module dependencies. */ var utils = require('./util/utils'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var EventEmitter = require('events').EventEmitter; var events = require('./util/events'); var appUtil = require('./util/appUtil'); var Constants = require('./util/constants'); var appManager = require('./common/manager/appManager'); var fs = require('fs'); var path = require('path'); /** * Application prototype. * * @module */ var Application = module.exports = {}; /** * Application states */ var STATE_INITED = 1; // app has inited var STATE_START = 2; // app start var STATE_STARTED = 3; // app has started var STATE_STOPED = 4; // app has stoped /** * Initialize the server. * * - setup default configuration */ Application.init = function(opts) { opts = opts || {}; this.loaded = []; // loaded component list this.components = {}; // name -> component map this.settings = {}; // collection keep set/get var base = opts.base || path.dirname(require.main.filename); this.set(Constants.RESERVED.BASE, base, true); this.event = new EventEmitter(); // event object to sub/pub events // current server info this.serverId = null; // current server id this.serverType = null; // current server type this.curServer = null; // current server info this.startTime = null; // current server start time // global server infos this.master = null; // master server info this.servers = {}; // current global server info maps, id -> info this.serverTypeMaps = {}; // current global type maps, type -> [info] this.serverTypes = []; // current global server type list this.lifecycleCbs = {}; // current server custom lifecycle callbacks this.clusterSeq = {}; // cluster id seqence appUtil.defaultConfiguration(this); this.state = STATE_INITED; logger.info('application inited: %j', this.getServerId()); }; /** * Get application base path * * // cwd: /home/game/ * pomelo start * // app.getBase() -> /home/game * * @return {String} application base path * * @memberOf Application */ Application.getBase = function() { return this.get(Constants.RESERVED.BASE); }; /** * Override require method in application * * @param {String} relative path of file * * @memberOf Application */ Application.require = function(ph) { return require(path.join(Application.getBase(), ph)); }; /** * Configure logger with {$base}/config/log4js.json * * @param {Object} logger pomelo-logger instance without configuration * * @memberOf Application */ Application.configureLogger = function(logger) { if (process.env.POMELO_LOGGER !== 'off') { var base = this.getBase(); var env = this.get(Constants.RESERVED.ENV); var originPath = path.join(base, Constants.FILEPATH.LOG); var presentPath = path.join(base, Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.LOG)); if(fs.existsSync(originPath)) { logger.configure(originPath, {serverId: this.serverId, base: base}); } else if(fs.existsSync(presentPath)) { logger.configure(presentPath, {serverId: this.serverId, base: base}); } else { logger.error('logger file path configuration is error.'); } } }; /** * add a filter to before and after filter * * @param {Object} filter provide before and after filter method. * A filter should have two methods: before and after. * @memberOf Application */ Application.filter = function (filter) { this.before(filter); this.after(filter); }; /** * Add before filter. * * @param {Object|Function} bf before fileter, bf(msg, session, next) * @memberOf Application */ Application.before = function (bf) { addFilter(this, Constants.KEYWORDS.BEFORE_FILTER, bf); }; /** * Add after filter. * * @param {Object|Function} af after filter, `af(err, msg, session, resp, next)` * @memberOf Application */ Application.after = function (af) { addFilter(this, Constants.KEYWORDS.AFTER_FILTER, af); }; /** * add a global filter to before and after global filter * * @param {Object} filter provide before and after filter method. * A filter should have two methods: before and after. * @memberOf Application */ Application.globalFilter = function (filter) { this.globalBefore(filter); this.globalAfter(filter); }; /** * Add global before filter. * * @param {Object|Function} bf before fileter, bf(msg, session, next) * @memberOf Application */ Application.globalBefore = function (bf) { addFilter(this, Constants.KEYWORDS.GLOBAL_BEFORE_FILTER, bf); }; /** * Add global after filter. * * @param {Object|Function} af after filter, `af(err, msg, session, resp, next)` * @memberOf Application */ Application.globalAfter = function (af) { addFilter(this, Constants.KEYWORDS.GLOBAL_AFTER_FILTER, af); }; /** * Add rpc before filter. * * @param {Object|Function} bf before fileter, bf(serverId, msg, opts, next) * @memberOf Application */ Application.rpcBefore = function(bf) { addFilter(this, Constants.KEYWORDS.RPC_BEFORE_FILTER, bf); }; /** * Add rpc after filter. * * @param {Object|Function} af after filter, `af(serverId, msg, opts, next)` * @memberOf Application */ Application.rpcAfter = function(af) { addFilter(this, Constants.KEYWORDS.RPC_AFTER_FILTER, af); }; /** * add a rpc filter to before and after rpc filter * * @param {Object} filter provide before and after filter method. * A filter should have two methods: before and after. * @memberOf Application */ Application.rpcFilter = function(filter) { this.rpcBefore(filter); this.rpcAfter(filter); }; /** * Load component * * @param {String} name (optional) name of the component * @param {Object} component component instance or factory function of the component * @param {[type]} opts (optional) construct parameters for the factory function * @return {Object} app instance for chain invoke * @memberOf Application */ Application.load = function(name, component, opts) { if(typeof name !== 'string') { opts = component; component = name; name = null; if(typeof component.name === 'string') { name = component.name; } } if(typeof component === 'function') { component = component(this, opts); } if(!name && typeof component.name === 'string') { name = component.name; } if(name && this.components[name]) { // ignore duplicat component logger.warn('ignore duplicate component: %j', name); return; } this.loaded.push(component); if(name) { // components with a name would get by name throught app.components later. this.components[name] = component; } return this; }; /** * Load Configure json file to settings.(support different enviroment directory & compatible for old path) * * @param {String} key environment key * @param {String} val environment value * @param {Boolean} reload whether reload after change default false * @return {Server|Mixed} for chaining, or the setting value * @memberOf Application */ Application.loadConfigBaseApp = function (key, val, reload) { var self = this; var env = this.get(Constants.RESERVED.ENV); var originPath = path.join(Application.getBase(), val); var presentPath = path.join(Application.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val)); var realPath; if(fs.existsSync(originPath)) { realPath = originPath; var file = require(originPath); if (file[env]) { file = file[env]; } this.set(key, file); } else if(fs.existsSync(presentPath)) { realPath = presentPath; var pfile = require(presentPath); this.set(key, pfile); } else { logger.error('invalid configuration with file path: %s', key); } if(!!realPath && !!reload) { fs.watch(realPath, function (event, filename) { if(event === 'change') { delete require.cache[require.resolve(realPath)]; self.loadConfigBaseApp(key, val); } }); } }; /** * Load Configure json file to settings. * * @param {String} key environment key * @param {String} val environment value * @return {Server|Mixed} for chaining, or the setting value * @memberOf Application */ Application.loadConfig = function(key, val) { var env = this.get(Constants.RESERVED.ENV); val = require(val); if (val[env]) { val = val[env]; } this.set(key, val); }; /** * Set the route function for the specified server type. * * Examples: * * app.route('area', routeFunc); * * var routeFunc = function(session, msg, app, cb) { * // all request to area would be route to the first area server * var areas = app.getServersByType('area'); * cb(null, areas[0].id); * }; * * @param {String} serverType server type string * @param {Function} routeFunc route function. routeFunc(session, msg, app, cb) * @return {Object} current application instance for chain invoking * @memberOf Application */ Application.route = function(serverType, routeFunc) { var routes = this.get(Constants.KEYWORDS.ROUTE); if(!routes) { routes = {}; this.set(Constants.KEYWORDS.ROUTE, routes); } routes[serverType] = routeFunc; return this; }; /** * Set before stop function. It would perform before servers stop. * * @param {Function} fun before close function * @return {Void} * @memberOf Application */ Application.beforeStopHook = function(fun) { logger.warn('this method was deprecated in pomelo 0.8'); if(!!fun && typeof fun === 'function') { this.set(Constants.KEYWORDS.BEFORE_STOP_HOOK, fun); } }; /** * Start application. It would load the default components and start all the loaded components. * * @param {Function} cb callback function * @memberOf Application */ Application.start = function(cb) { this.startTime = Date.now(); if(this.state > STATE_INITED) { utils.invokeCallback(cb, new Error('application has already start.')); return; } var self = this; appUtil.startByType(self, function() { appUtil.loadDefaultComponents(self); var startUp = function() { appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) { self.state = STATE_START; if(err) { utils.invokeCallback(cb, err); } else { logger.info('%j enter after start...', self.getServerId()); self.afterStart(cb); } }); }; var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP]; if(!!beforeFun) { beforeFun.call(null, self, startUp); } else { startUp(); } }); }; /** * Lifecycle callback for after start. * * @param {Function} cb callback function * @return {Void} */ Application.afterStart = function(cb) { if(this.state !== STATE_START) { utils.invokeCallback(cb, new Error('application is not running now.')); return; } var afterFun = this.lifecycleCbs[Constants.LIFECYCLE.AFTER_STARTUP]; var self = this; appUtil.optComponents(this.loaded, Constants.RESERVED.AFTER_START, function(err) { self.state = STATE_STARTED; var id = self.getServerId(); if(!err) { logger.info('%j finish start', id); } if(!!afterFun) { afterFun.call(null, self, function() { utils.invokeCallback(cb, err); }); } else { utils.invokeCallback(cb, err); } var usedTime = Date.now() - self.startTime; logger.info('%j startup in %s ms', id, usedTime); self.event.emit(events.START_SERVER, id); }); }; /** * Stop components. * * @param {Boolean} force whether stop the app immediately */ Application.stop = function(force) { if(this.state > STATE_STARTED) { logger.warn('[pomelo application] application is not running now.'); return; } this.state = STATE_STOPED; var self = this; this.stopTimer = setTimeout(function() { process.exit(0); }, Constants.TIME.TIME_WAIT_STOP); var cancelShutDownTimer =function(){ if(!!self.stopTimer) { clearTimeout(self.stopTimer); } }; var shutDown = function() { appUtil.stopComps(self.loaded, 0, force, function() { cancelShutDownTimer(); if(force) { process.exit(0); } }); }; var fun = this.get(Constants.KEYWORDS.BEFORE_STOP_HOOK); var stopFun = this.lifecycleCbs[Constants.LIFECYCLE.BEFORE_SHUTDOWN]; if(!!stopFun) { stopFun.call(null, this, shutDown, cancelShutDownTimer); } else if(!!fun) { utils.invokeCallback(fun, self, shutDown, cancelShutDownTimer); } else { shutDown(); } }; /** * Assign `setting` to `val`, or return `setting`'s value. * * Example: * * app.set('key1', 'value1'); * app.get('key1'); // 'value1' * app.key1; // undefined * * app.set('key2', 'value2', true); * app.get('key2'); // 'value2' * app.key2; // 'value2' * * @param {String} setting the setting of application * @param {String} val the setting's value * @param {Boolean} attach whether attach the settings to application * @return {Server|Mixed} for chaining, or the setting value * @memberOf Application */ Application.set = function (setting, val, attach) { if (arguments.length === 1) { return this.settings[setting]; } this.settings[setting] = val; if(attach) { this[setting] = val; } return this; }; /** * Get property from setting * * @param {String} setting application setting * @return {String} val * @memberOf Application */ Application.get = function (setting) { return this.settings[setting]; }; /** * Check if `setting` is enabled. * * @param {String} setting application setting * @return {Boolean} * @memberOf Application */ Application.enabled = function (setting) { return !!this.get(setting); }; /** * Check if `setting` is disabled. * * @param {String} setting application setting * @return {Boolean} * @memberOf Application */ Application.disabled = function (setting) { return !this.get(setting); }; /** * Enable `setting`. * * @param {String} setting application setting * @return {app} for chaining * @memberOf Application */ Application.enable = function (setting) { return this.set(setting, true); }; /** * Disable `setting`. * * @param {String} setting application setting * @return {app} for chaining * @memberOf Application */ Application.disable = function (setting) { return this.set(setting, false); }; /** * Configure callback for the specified env and server type. * When no env is specified that callback will * be invoked for all environments and when no type is specified * that callback will be invoked for all server types. * * Examples: * * app.configure(function(){ * // executed for all envs and server types * }); * * app.configure('development', function(){ * // executed development env * }); * * app.configure('development', 'connector', function(){ * // executed for development env and connector server type * }); * * @param {String} env application environment * @param {Function} fn callback function * @param {String} type server type * @return {Application} for chaining * @memberOf Application */ Application.configure = function (env, type, fn) { var args = [].slice.call(arguments); fn = args.pop(); env = type = Constants.RESERVED.ALL; if(args.length > 0) { env = args[0]; } if(args.length > 1) { type = args[1]; } if (env === Constants.RESERVED.ALL || contains(this.settings.env, env)) { if (type === Constants.RESERVED.ALL || contains(this.settings.serverType, type)) { fn.call(this); } } return this; }; /** * Register admin modules. Admin modules is the extends point of the monitor system. * * @param {String} module (optional) module id or provoided by module.moduleId * @param {Object} module module object or factory function for module * @param {Object} opts construct parameter for module * @memberOf Application */ Application.registerAdmin = function(moduleId, module, opts) { var modules = this.get(Constants.KEYWORDS.MODULE); if(!modules) { modules = {}; this.set(Constants.KEYWORDS.MODULE, modules); } if(typeof moduleId !== 'string') { opts = module; module = moduleId; if(module) { moduleId = module.moduleId; } } if(!moduleId){ return; } modules[moduleId] = { moduleId: moduleId, module: module, opts: opts }; }; /** * Use plugin. * * @param {Object} plugin plugin instance * @param {[type]} opts (optional) construct parameters for the factory function * @memberOf Application */ Application.use = function(plugin, opts) { if(!plugin.components) { logger.error('invalid components, no components exist'); return; } var self = this; opts = opts || {}; var dir = path.dirname(plugin.components); if(!fs.existsSync(plugin.components)) { logger.error('fail to find components, find path: %s', plugin.components); return; } fs.readdirSync(plugin.components).forEach(function (filename) { if (!/\.js$/.test(filename)) { return; } var name = path.basename(filename, '.js'); var param = opts[name] || {}; var absolutePath = path.join(dir, Constants.DIR.COMPONENT, filename); if(!fs.existsSync(absolutePath)) { logger.error('component %s not exist at %s', name, absolutePath); } else { self.load(require(absolutePath), param); } }); // load events if(!plugin.events) { return; } else { if(!fs.existsSync(plugin.events)) { logger.error('fail to find events, find path: %s', plugin.events); return; } fs.readdirSync(plugin.events).forEach(function (filename) { if (!/\.js$/.test(filename)) { return; } var absolutePath = path.join(dir, Constants.DIR.EVENT, filename); if(!fs.existsSync(absolutePath)) { logger.error('events %s not exist at %s', filename, absolutePath); } else { bindEvents(require(absolutePath), self); } }); } }; /** * Application transaction. Transcation includes conditions and handlers, if conditions are satisfied, handlers would be executed. * And you can set retry times to execute handlers. The transaction log is in file logs/transaction.log. * * @param {String} name transaction name * @param {Object} conditions functions which are called before transaction * @param {Object} handlers functions which are called during transaction * @param {Number} retry retry times to execute handlers if conditions are successfully executed * @memberOf Application */ Application.transaction = function(name, conditions, handlers, retry) { appManager.transaction(name, conditions, handlers, retry); }; /** * Get master server info. * * @return {Object} master server info, {id, host, port} * @memberOf Application */ Application.getMaster = function() { return this.master; }; /** * Get current server info. * * @return {Object} current server info, {id, serverType, host, port} * @memberOf Application */ Application.getCurServer = function() { return this.curServer; }; /** * Get current server id. * * @return {String|Number} current server id from servers.json * @memberOf Application */ Application.getServerId = function() { return this.serverId; }; /** * Get current server type. * * @return {String|Number} current server type from servers.json * @memberOf Application */ Application.getServerType = function() { return this.serverType; }; /** * Get all the current server infos. * * @return {Object} server info map, key: server id, value: server info * @memberOf Application */ Application.getServers = function() { return this.servers; }; /** * Get all server infos from servers.json. * * @return {Object} server info map, key: server id, value: server info * @memberOf Application */ Application.getServersFromConfig = function() { return this.get(Constants.KEYWORDS.SERVER_MAP); }; /** * Get all the server type. * * @return {Array} server type list * @memberOf Application */ Application.getServerTypes = function() { return this.serverTypes; }; /** * Get server info by server id from current server cluster. * * @param {String} serverId server id * @return {Object} server info or undefined * @memberOf Application */ Application.getServerById = function(serverId) { return this.servers[serverId]; }; /** * Get server info by server id from servers.json. * * @param {String} serverId server id * @return {Object} server info or undefined * @memberOf Application */ Application.getServerFromConfig = function(serverId) { return this.get(Constants.KEYWORDS.SERVER_MAP)[serverId]; }; /** * Get server infos by server type. * * @param {String} serverType server type * @return {Array} server info list * @memberOf Application */ Application.getServersByType = function(serverType) { return this.serverTypeMaps[serverType]; }; /** * Check the server whether is a frontend server * * @param {server} server server info. it would check current server * if server not specified * @return {Boolean} * * @memberOf Application */ Application.isFrontend = function(server) { server = server || this.getCurServer(); return !!server && server.frontend === 'true'; }; /** * Check the server whether is a backend server * * @param {server} server server info. it would check current server * if server not specified * @return {Boolean} * @memberOf Application */ Application.isBackend = function(server) { server = server || this.getCurServer(); return !!server && !server.frontend; }; /** * Check whether current server is a master server * * @return {Boolean} * @memberOf Application */ Application.isMaster = function() { return this.serverType === Constants.RESERVED.MASTER; }; /** * Add new server info to current application in runtime. * * @param {Array} servers new server info list * @memberOf Application */ Application.addServers = function(servers) { if(!servers || !servers.length) { return; } var item, slist; for(var i=0, l=servers.length; i 0 && flag; }, function(callback) { var j = 0; retry--; async.forEachSeries(dmethods, function(method, cb) { method(cb); transactionLogger.info('[%s]:[%s] handler is executed.', name, dnames[j]); j++; }, function(err) { if(err) { process.nextTick(function() { transactionLogger.error('[%s]:[%s]:[%s] handler is executed with err: %j.', name, dnames[--j], times-retry, err.stack); var log = { name: name, method: dnames[j], retry: times-retry, time: Date.now(), type: 'handler', description: err.stack }; transactionErrorLogger.error(JSON.stringify(log)); utils.invokeCallback(callback); }); return; } flag = false; utils.invokeCallback(callback); process.nextTick(function() { transactionLogger.info('[%s] all conditions and handlers are executed successfully.', name); }); }); }, function(err) { if(err) { logger.error('transaction process is executed with error: %j', err); } // callback will not pass error } ); }); } }); }; ================================================ FILE: lib/common/manager/taskManager.js ================================================ var sequeue = require('seq-queue'); var manager = module.exports; var queues = {}; manager.timeout = 3000; /** * Add tasks into task group. Create the task group if it dose not exist. * * @param {String} key task key * @param {Function} fn task callback * @param {Function} ontimeout task timeout callback * @param {Number} timeout timeout for task */ manager.addTask = function(key, fn, ontimeout, timeout) { var queue = queues[key]; if(!queue) { queue = sequeue.createQueue(manager.timeout); queues[key] = queue; } return queue.push(fn, ontimeout, timeout); }; /** * Destroy task group * * @param {String} key task key * @param {Boolean} force whether close task group directly */ manager.closeQueue = function(key, force) { if(!queues[key]) { // ignore illeagle key return; } queues[key].close(force); delete queues[key]; }; ================================================ FILE: lib/common/remote/backend/msgRemote.js ================================================ var utils = require('../../../util/utils'); var logger = require('pomelo-logger').getLogger('forward-log', __filename); /** * Remote service for backend servers. * Receive and handle request message forwarded from frontend server. */ module.exports = function(app) { return new Remote(app); }; var Remote = function(app) { this.app = app; }; /** * Forward message from frontend server to other server's handlers * * @param msg {Object} request message * @param session {Object} session object for current request * @param cb {Function} callback function */ Remote.prototype.forwardMessage = function(msg, session, cb) { var server = this.app.components.__server__; var sessionService = this.app.components.__backendSession__; if(!server) { logger.error('server component not enable on %s', this.app.serverId); utils.invokeCallback(cb, new Error('server component not enable')); return; } if(!sessionService) { logger.error('backend session component not enable on %s', this.app.serverId); utils.invokeCallback(cb, new Error('backend sesssion component not enable')); return; } // generate backend session for current request var backendSession = sessionService.create(session); // handle the request logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg); server.handle(msg, backendSession, function(err, resp, opts) { // cb && cb(err, resp, opts); utils.invokeCallback(cb, err, resp, opts); }); }; Remote.prototype.forwardMessage2 = function(route, body, aesPassword, compressGzip, session, cb) { var server = this.app.components.__server__; var sessionService = this.app.components.__backendSession__; if(!server) { logger.error('server component not enable on %s', this.app.serverId); utils.invokeCallback(cb, new Error('server component not enable')); return; } if(!sessionService) { logger.error('backend session component not enable on %s', this.app.serverId); utils.invokeCallback(cb, new Error('backend sesssion component not enable')); return; } // generate backend session for current request var backendSession = sessionService.create(session); // handle the request // logger.debug('backend server [%s] handle message: %j', this.app.serverId, msg); var dmsg = { route: route, body: body, compressGzip: compressGzip } var socket = { aesPassword: aesPassword } var connector = this.app.components.__connector__.connector; connector.runDecode(dmsg, socket, function(err, msg) { if(err) { return cb(err); } server.handle(msg, backendSession, function(err, resp, opts) { utils.invokeCallback(cb, err, resp, opts); }); }); }; ================================================ FILE: lib/common/remote/frontend/channelRemote.js ================================================ /** * Remote channel service for frontend server. * Receive push request from backend servers and push it to clients. */ var utils = require('../../../util/utils'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); module.exports = function(app) { return new Remote(app); }; var Remote = function(app) { this.app = app; }; /** * Push message to client by uids. * * @param {String} route route string of message * @param {Object} msg message * @param {Array} uids user ids that would receive the message * @param {Object} opts push options * @param {Function} cb callback function */ Remote.prototype.pushMessage = function(route, msg, uids, opts, cb) { if(!msg){ logger.error('Can not send empty message! route : %j, compressed msg : %j', route, msg); utils.invokeCallback(cb, new Error('can not send empty message.')); return; } var connector = this.app.components.__connector__; var sessionService = this.app.get('sessionService'); var fails = [], sids = [], sessions, j, k; for(var i=0, l=uids.length; i ST_INITED) { return false; } else { var res = add(uid, sid, this.groups); if(res) { this.records[uid] = {sid: sid, uid: uid}; this.userAmount =this.userAmount+1; } addToStore(this.__channelService__, genKey(this.__channelService__, this.name), genValue(sid, uid)); return res; } }; /** * Remove user from channel. * * @param {Number} uid user id * @param {String} sid frontend server id which user has connected to. * @return [Boolean] true if success or false if fail */ Channel.prototype.leave = function(uid, sid) { if(!uid || !sid) { return false; } var res = deleteFrom(uid, sid, this.groups[sid]); if(res){ delete this.records[uid]; this.userAmount = this.userAmount-1; } if(this.userAmount<0) this.userAmount=0;//robust removeFromStore(this.__channelService__, genKey(this.__channelService__, this.name), genValue(sid, uid)); if(this.groups[sid] && this.groups[sid].length === 0) { delete this.groups[sid]; } return res; }; /** * Get channel UserAmount in a channel. * * @return {number } channel member amount */ Channel.prototype.getUserAmount = function() { return this.userAmount; }; /** * Get channel members. * * Notice: Heavy operation. * * @return {Array} channel member uid list */ Channel.prototype.getMembers = function() { var res = [], groups = this.groups; var group, i, l; for(var sid in groups) { group = groups[sid]; for(i=0, l=group.length; i 0) { sendMessage(sid); } else { // empty group process.nextTick(rpcCB(sid)); } } }; var restoreChannel = function(self, cb) { if(!self.store) { utils.invokeCallback(cb); return; } else { loadAllFromStore(self, genKey(self), function(err, list) { if(!!err) { utils.invokeCallback(cb, err); return; } else { if(!list.length || !Array.isArray(list)) { utils.invokeCallback(cb); return; } var load = function(key) { return (function() { loadAllFromStore(self, key, function(err, items) { for(var j=0; j= self.befores.length) { cb(err, resp, opts); return; } var handler = self.befores[index++]; if(typeof handler === 'function') { handler(msg, session, next); } else if(typeof handler.before === 'function') { handler.before(msg, session, next); } else { logger.error('meet invalid before filter, handler or handler.before should be function.'); next(new Error('invalid before filter.')); } }; //end of next next(); }; /** * Do after filter chain. * Give server a chance to do clean up jobs after request responsed. * After filter can not change the request flow before. * After filter should call the next callback to let the request pass to next after filter. * * @param err {Object} error object * @param session {Object} session object for current request * @param {Object} resp response object send to client * @param cb {Function} cb(err) callback function to invoke next chain node */ Service.prototype.afterFilter = function(err, msg, session, resp, cb) { var index = 0, self = this; function next(err) { //if done if(index >= self.afters.length) { cb(err); return; } var handler = self.afters[index++]; if(typeof handler === 'function') { handler(err, msg, session, resp, next); } else if(typeof handler.after === 'function') { handler.after(err, msg, session, resp, next); } else { logger.error('meet invalid after filter, handler or handler.after should be function.'); next(new Error('invalid after filter.')); } } //end of next next(err); }; ================================================ FILE: lib/common/service/handlerService.js ================================================ var fs = require('fs'); var utils = require('../../util/utils'); var Loader = require('pomelo-loader'); var pathUtil = require('../../util/pathUtil'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var forwardLogger = require('pomelo-logger').getLogger('forward-log', __filename); /** * Handler service. * Dispatch request to the relactive handler. * * @param {Object} app current application context */ var Service = function(app, opts) { this.app = app; this.handlerMap = {}; if(!!opts.reloadHandlers) { watchHandlers(app, this.handlerMap); } this.enableForwardLog = opts.enableForwardLog || false; }; module.exports = Service; Service.prototype.name = 'handler'; /** * Handler the request. */ Service.prototype.handle = function(routeRecord, msg, session, cb) { // the request should be processed by current server var handler = this.getHandler(routeRecord); if(!handler) { logger.error('[handleManager]: fail to find handler for %j', msg.__route__); utils.invokeCallback(cb, new Error('fail to find handler for ' + msg.__route__)); return; } var start = Date.now(); var self = this; var callback = function(err, resp, opts) { if(self.enableForwardLog) { var log = { route : msg.__route__, args : msg, time : utils.format(new Date(start)), timeUsed : new Date() - start }; forwardLogger.info(JSON.stringify(log)); } // resp = getResp(arguments); utils.invokeCallback(cb, err, resp, opts); } var method = routeRecord.method; if(!Array.isArray(msg)) { handler[method](msg, session, callback); } else { msg.push(session); msg.push(callback); handler[method].apply(handler, msg); } return; }; /** * Get handler instance by routeRecord. * * @param {Object} handlers handler map * @param {Object} routeRecord route record parsed from route string * @return {Object} handler instance if any matchs or null for match fail */ Service.prototype.getHandler = function(routeRecord) { var serverType = routeRecord.serverType; if(!this.handlerMap[serverType]) { loadHandlers(this.app, serverType, this.handlerMap); } var handlers = this.handlerMap[serverType] || {}; var handler = handlers[routeRecord.handler]; if(!handler) { logger.warn('could not find handler for routeRecord: %j', routeRecord); return null; } if(typeof handler[routeRecord.method] !== 'function') { logger.warn('could not find the method %s in handler: %s', routeRecord.method, routeRecord.handler); return null; } return handler; }; /** * Load handlers from current application */ var loadHandlers = function(app, serverType, handlerMap) { var p = pathUtil.getHandlerPath(app.getBase(), serverType); if(p) { handlerMap[serverType] = Loader.load(p, app); } }; var watchHandlers = function(app, handlerMap) { var p = pathUtil.getHandlerPath(app.getBase(), app.serverType); if (!!p){ fs.watch(p, function(event, name) { if(event === 'change') { handlerMap[app.serverType] = Loader.load(p, app); } }); } }; var getResp = function(args) { var len = args.length; if(len == 1) { return []; } if(len == 2) { return [args[1]]; } if(len == 3) { return [args[1], args[2]]; } if(len == 4) { return [args[1], args[2], args[3]]; } var r = new Array(len); for (var i = 1; i < len; i++) { r[i] = args[i]; } return r; } ================================================ FILE: lib/common/service/sessionService.js ================================================ var EventEmitter = require('events').EventEmitter; var util = require('util'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var utils = require('../../util/utils'); var FRONTEND_SESSION_FIELDS = ['id', 'frontendId', 'uid', '__sessionService__']; var EXPORTED_SESSION_FIELDS = ['id', 'frontendId', 'uid', 'settings']; var ST_INITED = 0; var ST_CLOSED = 1; /** * Session service maintains the internal session for each client connection. * * Session service is created by session component and is only * available in frontend servers. You can access the service by * `app.get('sessionService')` or `app.sessionService` in frontend servers. * * @param {Object} opts constructor parameters * @class * @constructor */ var SessionService = function(opts) { opts = opts || {}; this.singleSession = opts.singleSession; this.sessions = {}; // sid -> session this.uidMap = {}; // uid -> sessions }; module.exports = SessionService; /** * Create and return internal session. * * @param {Integer} sid uniqe id for the internal session * @param {String} frontendId frontend server in which the internal session is created * @param {Object} socket the underlying socket would be held by the internal session * * @return {Session} * * @memberOf SessionService * @api private */ SessionService.prototype.create = function(sid, frontendId, socket) { var session = new Session(sid, frontendId, socket, this); this.sessions[session.id] = session; return session; }; /** * Bind the session with a user id. * * @memberOf SessionService * @api private */ SessionService.prototype.bind = function(sid, uid, cb) { var session = this.sessions[sid]; if(!session) { process.nextTick(function() { cb(new Error('session does not exist, sid: ' + sid)); }); return; } if(session.uid) { if(session.uid === uid) { // already bound with the same uid cb(); return; } // already bound with other uid process.nextTick(function() { cb(new Error('session has already bind with ' + session.uid)); }); return; } var sessions = this.uidMap[uid]; if(!!this.singleSession && !!sessions) { process.nextTick(function() { cb(new Error('singleSession is enabled, and session has already bind with uid: ' + uid)); }); return; } if(!sessions) { sessions = this.uidMap[uid] = []; } for(var i=0, l=sessions.length; i maxConnections) { logger.warn('the server %s has reached the max connections %s', curServer.id, maxConnections); socket.disconnect(); return; } } //create session for connection var session = getSession(self, socket); var closed = false; socket.on('disconnect', function() { if (closed) { return; } closed = true; if (self.connection) { self.connection.decreaseConnectionCount(session.uid); } }); socket.on('error', function() { if (closed) { return; } closed = true; if (self.connection) { self.connection.decreaseConnectionCount(session.uid); } }); // new message socket.on('message', function(msg) { var dmsg = msg; if (self.useAsyncCoder) { return handleMessageAsync(self, msg, session, socket); } if (self.decode) { dmsg = self.decode(msg, session); } else if (self.connector.decode) { dmsg = self.connector.decode(msg, socket); } if (!dmsg) { // discard invalid message return; } // use rsa crypto if (self.useCrypto) { var verified = verifyMessage(self, session, dmsg); if (!verified) { logger.error('fail to verify the data received from client.'); return; } } handleMessage(self, session, dmsg); }); //on message end }; var handleMessageAsync = function(self, msg, session, socket) { if (self.decode) { self.decode(msg, session, function(err, dmsg) { if (err) { logger.error('fail to decode message from client %s .', err.stack); return; } doHandleMessage(self, dmsg, session); }); } else if (self.connector.decode) { self.connector.decode(msg, socket, function(err, dmsg) { if (err) { logger.error('fail to decode message from client %s .', err.stack); return; } doHandleMessage(self, dmsg, session); }); } } var doHandleMessage = function(self, dmsg, session) { if (!dmsg) { // discard invalid message return; } // use rsa crypto if (self.useCrypto) { var verified = verifyMessage(self, session, dmsg); if (!verified) { logger.error('fail to verify the data received from client.'); return; } } handleMessage(self, session, dmsg); } /** * get session for current connection */ var getSession = function(self, socket) { var app = self.app, sid = socket.id; var session = self.session.get(sid); if (session) { return session; } session = self.session.create(sid, app.getServerId(), socket); logger.debug('[%s] getSession session is created with session id: %s', app.getServerId(), sid); // bind events for session socket.on('disconnect', session.closed.bind(session)); socket.on('error', session.closed.bind(session)); session.on('closed', onSessionClose.bind(null, app)); session.on('bind', function(uid) { logger.debug('session on [%s] bind with uid: %s', self.app.serverId, uid); // update connection statistics if necessary if (self.connection) { self.connection.addLoginedUser(uid, { loginTime: Date.now(), uid: uid, address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port }); } self.app.event.emit(events.BIND_SESSION, session); }); session.on('unbind', function(uid) { if (self.connection) { self.connection.removeLoginedUser(uid); } self.app.event.emit(events.UNBIND_SESSION, session); }); return session; }; var onSessionClose = function(app, session, reason) { taskManager.closeQueue(session.id, true); app.event.emit(events.CLOSE_SESSION, session); }; var handleMessage = function(self, session, msg) { logger.debug('[%s] handleMessage session id: %s, msg: %j', self.app.serverId, session.id, msg); var type = checkServerType(msg.route); if (!type) { logger.error('invalid route string. route : %j', msg.route); return; } self.server.globalHandle(msg, session.toFrontendSession(), function(err, resp, opts) { if (resp && !msg.id) { logger.warn('try to response to a notify: %j', msg.route); return; } if (!msg.id && !resp) return; if (!resp) resp = {}; if (!!err && !resp.code) { resp.code = 500; } opts = { type: 'response', userOptions: opts || {} }; // for compatiablity opts.isResponse = true; self.send(msg.id, msg.route, resp, [session.id], opts, function() {}); }); }; /** * Get server type form request message. */ var checkServerType = function(route) { if (!route) { return null; } var idx = route.indexOf('.'); if (idx < 0) { return null; } return route.substring(0, idx); }; var verifyMessage = function(self, session, msg) { var sig = msg.body.__crypto__; if (!sig) { logger.error('receive data from client has no signature [%s]', self.app.serverId); return false; } var pubKey; if (!session) { logger.error('could not find session.'); return false; } if (!session.get('pubKey')) { pubKey = self.getPubKey(session.id); if (!!pubKey) { delete self.keys[session.id]; session.set('pubKey', pubKey); } else { logger.error('could not get public key, session id is %s', session.id); return false; } } else { pubKey = session.get('pubKey'); } if (!pubKey.n || !pubKey.e) { logger.error('could not verify message without public key [%s]', self.app.serverId); return false; } delete msg.body.__crypto__; var message = JSON.stringify(msg.body); if (utils.hasChineseChar(message)) message = utils.unicodeToUtf8(message); return pubKey.verifyString(message, sig); }; ================================================ FILE: lib/components/dictionary.js ================================================ var fs = require('fs'); var path = require('path'); var utils = require('../util/utils'); var Loader = require('pomelo-loader'); var pathUtil = require('../util/pathUtil'); var crypto = require('crypto'); module.exports = function(app, opts) { return new Component(app, opts); }; var Component = function(app, opts) { this.app = app; this.dict = {}; this.abbrs = {}; this.userDicPath = null; this.version = ""; //Set user dictionary var p = path.join(app.getBase(), '/config/dictionary.json'); if(!!opts && !!opts.dict) { p = opts.dict; } if(fs.existsSync(p)) { this.userDicPath = p; } }; var pro = Component.prototype; pro.name = '__dictionary__'; pro.start = function(cb) { var servers = this.app.get('servers'); var routes = []; //Load all the handler files for(var serverType in servers) { var p = pathUtil.getHandlerPath(this.app.getBase(), serverType); if(!p) { continue; } var handlers = Loader.load(p, this.app); for(var name in handlers) { var handler = handlers[name]; for(var key in handler) { if(typeof(handler[key]) === 'function') { routes.push(serverType + '.' + name + '.' + key); } } } } //Sort the route to make sure all the routers abbr are the same in all the servers routes.sort(); var abbr; var i; for(i = 0; i < routes.length; i++) { abbr = i + 1; this.abbrs[abbr] = routes[i]; this.dict[routes[i]] = abbr; } //Load user dictionary if(!!this.userDicPath) { var userDic = require(this.userDicPath); abbr = routes.length + 1; for(i = 0; i < userDic.length; i++) { var route = userDic[i]; this.abbrs[abbr] = route; this.dict[route] = abbr; abbr++; } } this.version = crypto.createHash('md5').update(JSON.stringify(this.dict)).digest('base64'); utils.invokeCallback(cb); }; pro.getDict = function() { return this.dict; }; pro.getAbbrs = function() { return this.abbrs; }; pro.getVersion = function() { return this.version; }; ================================================ FILE: lib/components/master.js ================================================ /** * Component for master. */ var Master = require('../master/master'); /** * Component factory function * * @param {Object} app current application context * @return {Object} component instances */ module.exports = function (app, opts) { return new Component(app, opts); }; /** * Master component class * * @param {Object} app current application context */ var Component = function (app, opts) { this.master = new Master(app, opts); }; var pro = Component.prototype; pro.name = '__master__'; /** * Component lifecycle function * * @param {Function} cb * @return {Void} */ pro.start = function (cb) { this.master.start(cb); }; /** * Component lifecycle function * * @param {Boolean} force whether stop the component immediately * @param {Function} cb * @return {Void} */ pro.stop = function (force, cb) { this.master.stop(cb); }; ================================================ FILE: lib/components/monitor.js ================================================ /** * Component for monitor. * Load and start monitor client. */ var Monitor = require('../monitor/monitor'); /** * Component factory function * * @param {Object} app current application context * @return {Object} component instances */ module.exports = function(app, opts) { return new Component(app, opts); }; var Component = function(app, opts) { this.monitor = new Monitor(app, opts); }; var pro = Component.prototype; pro.name = '__monitor__'; pro.start = function(cb) { this.monitor.start(cb); }; pro.stop = function(force, cb) { this.monitor.stop(cb); }; pro.reconnect = function(masterInfo) { this.monitor.reconnect(masterInfo); }; ================================================ FILE: lib/components/protobuf.js ================================================ var fs = require('fs'); var path = require('path'); var protobuf = require('pomelo-protobuf'); var Constants = require('../util/constants'); var crypto = require('crypto'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); module.exports = function(app, opts) { return new Component(app, opts); }; var Component = function(app, opts) { this.app = app; opts = opts || {}; this.watchers = {}; this.serverProtos = {}; this.clientProtos = {}; this.version = ""; var env = app.get(Constants.RESERVED.ENV); var originServerPath = path.join(app.getBase(), Constants.FILEPATH.SERVER_PROTOS); var presentServerPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.SERVER_PROTOS)); var originClientPath = path.join(app.getBase(), Constants.FILEPATH.CLIENT_PROTOS); var presentClientPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.CLIENT_PROTOS)); this.serverProtosPath = opts.serverProtos || (fs.existsSync(originServerPath) ? Constants.FILEPATH.SERVER_PROTOS : presentServerPath); this.clientProtosPath = opts.clientProtos || (fs.existsSync(originClientPath) ? Constants.FILEPATH.CLIENT_PROTOS : presentClientPath); this.setProtos(Constants.RESERVED.SERVER, path.join(app.getBase(), this.serverProtosPath)); this.setProtos(Constants.RESERVED.CLIENT, path.join(app.getBase(), this.clientProtosPath)); protobuf.init({encoderProtos:this.serverProtos, decoderProtos:this.clientProtos}); }; var pro = Component.prototype; pro.name = '__protobuf__'; pro.encode = function(key, msg) { return protobuf.encode(key, msg); }; pro.encode2Bytes = function(key, msg) { return protobuf.encode2Bytes(key, msg); }; pro.decode = function(key, msg) { return protobuf.decode(key, msg); }; pro.getProtos = function() { return { server : this.serverProtos, client : this.clientProtos, version : this.version }; }; pro.getVersion = function() { return this.version; }; pro.setProtos = function(type, path) { if(!fs.existsSync(path)) { return; } if(type === Constants.RESERVED.SERVER) { this.serverProtos = protobuf.parse(require(path)); } if(type === Constants.RESERVED.CLIENT) { this.clientProtos = protobuf.parse(require(path)); } var protoStr = JSON.stringify(this.clientProtos) + JSON.stringify(this.serverProtos); this.version = crypto.createHash('md5').update(protoStr).digest('base64'); //Watch file var watcher = fs.watch(path, this.onUpdate.bind(this, type, path)); if (this.watchers[type]) { this.watchers[type].close(); } this.watchers[type] = watcher; }; pro.onUpdate = function(type, path, event) { if(event !== 'change') { return; } var self = this; fs.readFile(path, 'utf8' ,function(err, data) { try { var protos = protobuf.parse(JSON.parse(data)); if(type === Constants.RESERVED.SERVER) { protobuf.setEncoderProtos(protos); self.serverProtos = protos; } else { protobuf.setDecoderProtos(protos); self.clientProtos = protos; } var protoStr = JSON.stringify(self.clientProtos) + JSON.stringify(self.serverProtos); self.version = crypto.createHash('md5').update(protoStr).digest('base64'); logger.info('change proto file , type : %j, path : %j, version : %j', type, path, self.version); } catch(e) { logger.warn("change proto file error! path : %j", path); logger.warn(e); } }); }; pro.stop = function(force, cb) { for (var type in this.watchers) { this.watchers[type].close(); } this.watchers = {}; process.nextTick(cb); }; ================================================ FILE: lib/components/proxy.js ================================================ /** * Component for proxy. * Generate proxies for rpc client. */ var crc = require('crc'); var utils = require('../util/utils'); var events = require('../util/events'); var Client = require('pomelo-rpc').client; var pathUtil = require('../util/pathUtil'); var Constants = require('../util/constants'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); /** * Component factory function * * @param {Object} app current application context * @param {Object} opts construct parameters * opts.router: (optional) rpc message route function, route(routeParam, msg, cb), * opts.mailBoxFactory: (optional) mail box factory instance. * @return {Object} component instance */ module.exports = function(app, opts) { opts = opts || {}; // proxy default config // cacheMsg is deprecated, just for compatibility here. opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false; opts.interval = opts.interval || 30; opts.router = genRouteFun(); opts.context = app; opts.routeContext = app; if (app.enabled('rpcDebugLog')) { opts.rpcDebugLog = true; opts.rpcLogger = require('pomelo-logger').getLogger('rpc-debug', __filename); } return new Component(app, opts); }; /** * Proxy component class * * @param {Object} app current application context * @param {Object} opts construct parameters */ var Component = function(app, opts) { this.app = app; this.opts = opts; this.client = genRpcClient(this.app, opts); this.app.event.on(events.ADD_SERVERS, this.addServers.bind(this)); this.app.event.on(events.REMOVE_SERVERS, this.removeServers.bind(this)); this.app.event.on(events.REPLACE_SERVERS, this.replaceServers.bind(this)); }; var pro = Component.prototype; pro.name = '__proxy__'; /** * Proxy component lifecycle function * * @param {Function} cb * @return {Void} */ pro.start = function(cb) { if(this.opts.enableRpcLog) { logger.warn('enableRpcLog is deprecated in 0.8.0, please use app.rpcFilter(pomelo.rpcFilters.rpcLog())'); } var rpcBefores = this.app.get(Constants.KEYWORDS.RPC_BEFORE_FILTER); var rpcAfters = this.app.get(Constants.KEYWORDS.RPC_AFTER_FILTER); var rpcErrorHandler = this.app.get(Constants.RESERVED.RPC_ERROR_HANDLER); if(!!rpcBefores) { this.client.before(rpcBefores); } if(!!rpcAfters) { this.client.after(rpcAfters); } if(!!rpcErrorHandler) { this.client.setErrorHandler(rpcErrorHandler); } process.nextTick(cb); }; /** * Component lifecycle callback * * @param {Function} cb * @return {Void} */ pro.afterStart = function(cb) { var self = this; this.app.__defineGetter__('rpc', function() { return self.client.proxies.user; }); this.app.__defineGetter__('sysrpc', function() { return self.client.proxies.sys; }); this.app.set('rpcInvoke', this.client.rpcInvoke.bind(this.client), true); this.client.start(cb); }; /** * Add remote server to the rpc client. * * @param {Array} servers server info list, {id, serverType, host, port} */ pro.addServers = function(servers) { if (!servers || !servers.length) { return; } genProxies(this.client, this.app, servers); this.client.addServers(servers); }; /** * Remove remote server from the rpc client. * * @param {Array} ids server id list */ pro.removeServers = function(ids) { this.client.removeServers(ids); }; /** * Replace remote servers from the rpc client. * * @param {Array} ids server id list */ pro.replaceServers = function(servers) { if (!servers || !servers.length) { return; } // update proxies this.client.proxies = {}; genProxies(this.client, this.app, servers); this.client.replaceServers(servers); }; /** * Proxy for rpc client rpcInvoke. * * @param {String} serverId remote server id * @param {Object} msg rpc message: {serverType: serverType, service: serviceName, method: methodName, args: arguments} * @param {Function} cb callback function */ pro.rpcInvoke = function(serverId, msg, cb) { this.client.rpcInvoke(serverId, msg, cb); }; /** * Generate rpc client * * @param {Object} app current application context * @param {Object} opts contructor parameters for rpc client * @return {Object} rpc client */ var genRpcClient = function(app, opts) { opts.context = app; opts.routeContext = app; if(!!opts.rpcClient) { return opts.rpcClient.create(opts); } else { return Client.create(opts); } }; /** * Generate proxy for the server infos. * * @param {Object} client rpc client instance * @param {Object} app application context * @param {Array} sinfos server info list */ var genProxies = function(client, app, sinfos) { var item; for (var i = 0, l = sinfos.length; i < l; i++) { item = sinfos[i]; if (hasProxy(client, item)) { continue; } client.addProxies(getProxyRecords(app, item)); } }; /** * Check a server whether has generated proxy before * * @param {Object} client rpc client instance * @param {Object} sinfo server info * @return {Boolean} true or false */ var hasProxy = function(client, sinfo) { var proxy = client.proxies; return !!proxy.sys && !! proxy.sys[sinfo.serverType]; }; /** * Get proxy path for rpc client. * Iterate all the remote service path and create remote path record. * * @param {Object} app current application context * @param {Object} sinfo server info, format: {id, serverType, host, port} * @return {Array} remote path record array */ var getProxyRecords = function(app, sinfo) { var records = [], appBase = app.getBase(), record; // sys remote service path record if (app.isFrontend(sinfo)) { record = pathUtil.getSysRemotePath('frontend'); } else { record = pathUtil.getSysRemotePath('backend'); } if (record) { records.push(pathUtil.remotePathRecord('sys', sinfo.serverType, record)); } // user remote service path record record = pathUtil.getUserRemotePath(appBase, sinfo.serverType); if (record) { records.push(pathUtil.remotePathRecord('user', sinfo.serverType, record)); } return records; }; var genRouteFun = function() { return function(session, msg, app, cb) { var routes = app.get('__routes__'); if (!routes) { defaultRoute(session, msg, app, cb); return; } var type = msg.serverType, route = routes[type] || routes['default']; if (route) { route(session, msg, app, cb); } else { defaultRoute(session, msg, app, cb); } }; }; var defaultRoute = function(session, msg, app, cb) { var list = app.getServersByType(msg.serverType); if (!list || !list.length) { cb(new Error('can not find server info for type:' + msg.serverType)); return; } var uid = session ? (session.uid || '') : ''; var index = Math.abs(crc.crc32(uid.toString())) % list.length; utils.invokeCallback(cb, null, list[index].id); }; ================================================ FILE: lib/components/pushScheduler.js ================================================ /** * Scheduler component to schedule message sending. */ var DefaultScheduler = require('../pushSchedulers/direct'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); module.exports = function(app, opts) { return new PushScheduler(app, opts); }; var PushScheduler = function(app, opts) { this.app = app; opts = opts || {}; this.scheduler = getScheduler(this, app, opts); }; PushScheduler.prototype.name = '__pushScheduler__'; /** * Component lifecycle callback * * @param {Function} cb * @return {Void} */ PushScheduler.prototype.afterStart = function(cb) { if(this.isSelectable) { for (var k in this.scheduler) { var sch = this.scheduler[k]; if(typeof sch.start === 'function') { sch.start(); } } process.nextTick(cb); } else if(typeof this.scheduler.start === 'function') { this.scheduler.start(cb); } else { process.nextTick(cb); } }; /** * Component lifecycle callback * * @param {Function} cb * @return {Void} */ PushScheduler.prototype.stop = function(force, cb) { if(this.isSelectable) { for (var k in this.scheduler) { var sch = this.scheduler[k]; if(typeof sch.stop === 'function') { sch.stop(); } } process.nextTick(cb); } else if(typeof this.scheduler.stop === 'function') { this.scheduler.stop(cb); } else { process.nextTick(cb); } }; /** * Schedule how the message to send. * * @param {Number} reqId request id * @param {String} route route string of the message * @param {Object} msg message content after encoded * @param {Array} recvs array of receiver's session id * @param {Object} opts options * @param {Function} cb */ PushScheduler.prototype.schedule = function(reqId, route, msg, recvs, opts, cb) { var self = this; if(self.isSelectable) { if(typeof self.selector === 'function') { self.selector(reqId, route, msg, recvs, opts, function(id) { if(self.scheduler[id] && typeof self.scheduler[id].schedule === 'function') { self.scheduler[id].schedule(reqId, route, msg, recvs, opts, cb); } else { logger.error('invalid pushScheduler id, id: %j', id); } }); } else { logger.error('the selector for pushScheduler is not a function, selector: %j', self.selector); } } else { if (typeof self.scheduler.schedule === 'function') { self.scheduler.schedule(reqId, route, msg, recvs, opts, cb); } else { logger.error('the scheduler does not have a schedule function, scheduler: %j', self.scheduler); } } }; var getScheduler = function(pushSchedulerComp, app, opts) { var scheduler = opts.scheduler || DefaultScheduler; if(typeof scheduler === 'function') { return scheduler(app, opts); } if(Array.isArray(scheduler)) { var res = {}; scheduler.forEach(function(sch) { if(typeof sch.scheduler === 'function') { res[sch.id] = sch.scheduler(app, sch.options); } else { res[sch.id] = sch.scheduler; } }); pushSchedulerComp.isSelectable = true; pushSchedulerComp.selector = opts.selector; return res; } return scheduler; }; ================================================ FILE: lib/components/remote.js ================================================ /** * Component for remote service. * Load remote service and add to global context. */ var fs = require('fs'); var pathUtil = require('../util/pathUtil'); var RemoteServer = require('pomelo-rpc').server; /** * Remote component factory function * * @param {Object} app current application context * @param {Object} opts construct parameters * opts.acceptorFactory {Object}: acceptorFactory.create(opts, cb) * @return {Object} remote component instances */ module.exports = function(app, opts) { opts = opts || {}; // cacheMsg is deprecated, just for compatibility here. opts.bufferMsg = opts.bufferMsg || opts.cacheMsg || false; opts.interval = opts.interval || 30; if(app.enabled('rpcDebugLog')) { opts.rpcDebugLog = true; opts.rpcLogger = require('pomelo-logger').getLogger('rpc-debug', __filename); } return new Component(app, opts); }; /** * Remote component class * * @param {Object} app current application context * @param {Object} opts construct parameters */ var Component = function(app, opts) { this.app = app; this.opts = opts; }; var pro = Component.prototype; pro.name = '__remote__'; /** * Remote component lifecycle function * * @param {Function} cb * @return {Void} */ pro.start = function(cb) { this.opts.port = this.app.getCurServer().port; this.remote = genRemote(this.app, this.opts); this.remote.start(); process.nextTick(cb); }; /** * Remote component lifecycle function * * @param {Boolean} force whether stop the component immediately * @param {Function} cb * @return {Void} */ pro.stop = function(force, cb) { this.remote.stop(force); process.nextTick(cb); }; /** * Get remote paths from application * * @param {Object} app current application context * @return {Array} paths * */ var getRemotePaths = function(app) { var paths = []; var role; // master server should not come here if(app.isFrontend()) { role = 'frontend'; } else { role = 'backend'; } var sysPath = pathUtil.getSysRemotePath(role), serverType = app.getServerType(); if(fs.existsSync(sysPath)) { paths.push(pathUtil.remotePathRecord('sys', serverType, sysPath)); } var userPath = pathUtil.getUserRemotePath(app.getBase(), serverType); if(fs.existsSync(userPath)) { paths.push(pathUtil.remotePathRecord('user', serverType, userPath)); } return paths; }; /** * Generate remote server instance * * @param {Object} app current application context * @param {Object} opts contructor parameters for rpc Server * @return {Object} remote server instance */ var genRemote = function(app, opts) { opts.paths = getRemotePaths(app); opts.context = app; if(!!opts.rpcServer) { return opts.rpcServer.create(opts); } else { return RemoteServer.create(opts); } }; ================================================ FILE: lib/components/server.js ================================================ /** * Component for server starup. */ var Server = require('../server/server'); /** * Component factory function * * @param {Object} app current application context * @return {Object} component instance */ module.exports = function(app, opts) { return new Component(app, opts); }; /** * Server component class * * @param {Object} app current application context */ var Component = function(app, opts) { this.server = Server.create(app, opts); }; var pro = Component.prototype; pro.name = '__server__'; /** * Component lifecycle callback * * @param {Function} cb * @return {Void} */ pro.start = function(cb) { this.server.start(); process.nextTick(cb); }; /** * Component lifecycle callback * * @param {Function} cb * @return {Void} */ pro.afterStart = function(cb) { this.server.afterStart(); process.nextTick(cb); }; /** * Component lifecycle function * * @param {Boolean} force whether stop the component immediately * @param {Function} cb * @return {Void} */ pro.stop = function(force, cb) { this.server.stop(); process.nextTick(cb); }; /** * Proxy server handle */ pro.handle = function(msg, session, cb) { this.server.handle(msg, session, cb); }; /** * Proxy server global handle */ pro.globalHandle = function(msg, session, cb) { this.server.globalHandle(msg, session, cb); }; ================================================ FILE: lib/components/session.js ================================================ var SessionService = require('../common/service/sessionService'); module.exports = function(app, opts) { var cmp = new Component(app, opts); app.set('sessionService', cmp, true); return cmp; }; /** * Session component. Manage sessions. * * @param {Object} app current application context * @param {Object} opts attach parameters */ var Component = function(app, opts) { opts = opts || {}; this.app = app; this.service = new SessionService(opts); var getFun = function(m) { return (function() { return function() { return self.service[m].apply(self.service, arguments); }; })(); }; // proxy the service methods except the lifecycle interfaces of component var method, self = this; for(var m in this.service) { if(m !== 'start' && m !== 'stop') { method = this.service[m]; if(typeof method === 'function') { this[m] = getFun(m); } } } }; Component.prototype.name = '__session__'; ================================================ FILE: lib/connectors/commands/handshake.js ================================================ var pomelo = require('../../pomelo'); var Package = require('pomelo-protocol').Package; var CODE_OK = 200; var CODE_USE_ERROR = 500; var CODE_OLD_CLIENT = 501; /** * Process the handshake request. * * @param {Object} opts option parameters * opts.handshake(msg, cb(err, resp)) handshake callback. msg is the handshake message from client. * opts.hearbeat heartbeat interval (level?) * opts.version required client level */ var Command = function(opts) { opts = opts || {}; this.userHandshake = opts.handshake; if(opts.heartbeat) { this.heartbeatSec = opts.heartbeat; this.heartbeat = opts.heartbeat * 1000; } this.checkClient = opts.checkClient; this.useDict = opts.useDict; this.useProtobuf = opts.useProtobuf; this.useCrypto = opts.useCrypto; }; module.exports = Command; Command.prototype.handle = function(socket, msg) { if(!msg.sys) { processError(socket, CODE_USE_ERROR); return; } if(typeof this.checkClient === 'function') { if(!msg || !msg.sys || !this.checkClient(msg.sys.type, msg.sys.version)) { processError(socket, CODE_OLD_CLIENT); return; } } var opts = { heartbeat : setupHeartbeat(this) }; if(this.useDict) { var dictVersion = pomelo.app.components.__dictionary__.getVersion(); if(!msg.sys.dictVersion || msg.sys.dictVersion !== dictVersion){ // may be deprecated in future opts.dict = pomelo.app.components.__dictionary__.getDict(); opts.routeToCode = pomelo.app.components.__dictionary__.getDict(); opts.codeToRoute = pomelo.app.components.__dictionary__.getAbbrs(); opts.dictVersion = dictVersion; } opts.useDict = true; } if(this.useProtobuf) { var protoVersion = pomelo.app.components.__protobuf__.getVersion(); if(!msg.sys.protoVersion || msg.sys.protoVersion !== protoVersion){ opts.protos = pomelo.app.components.__protobuf__.getProtos(); } opts.useProto = true; } if(!!pomelo.app.components.__decodeIO__protobuf__) { if(!!this.useProtobuf) { throw new Error('protobuf can not be both used in the same project.'); } var version = pomelo.app.components.__decodeIO__protobuf__.getVersion(); if(!msg.sys.protoVersion || msg.sys.protoVersion < version) { opts.protos = pomelo.app.components.__decodeIO__protobuf__.getProtos(); } opts.useProto = true; } if(this.useCrypto) { pomelo.app.components.__connector__.setPubKey(socket.id, msg.sys.rsa); } if(typeof this.userHandshake === 'function') { this.userHandshake(msg, function(err, resp) { if(err) { process.nextTick(function() { processError(socket, CODE_USE_ERROR); }); return; } process.nextTick(function() { response(socket, opts, resp); }); }, socket); return; } process.nextTick(function() { response(socket, opts); }); }; var setupHeartbeat = function(self) { return self.heartbeatSec; }; var response = function(socket, sys, resp) { var res = { code: CODE_OK, sys: sys }; if(resp) { res.user = resp; } socket.handshakeResponse(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res)))); }; var processError = function(socket, code) { var res = { code: code }; socket.sendForce(Package.encode(Package.TYPE_HANDSHAKE, new Buffer(JSON.stringify(res)))); process.nextTick(function() { socket.disconnect(); }); }; ================================================ FILE: lib/connectors/commands/heartbeat.js ================================================ var Package = require('pomelo-protocol').Package; var logger = require('pomelo-logger').getLogger('pomelo', __filename); /** * Process heartbeat request. * * @param {Object} opts option request * opts.heartbeat heartbeat interval */ var Command = function(opts) { opts = opts || {}; this.heartbeat = null; this.timeout = null; this.disconnectOnTimeout = opts.disconnectOnTimeout; if(opts.heartbeat) { this.heartbeat = opts.heartbeat * 1000; // heartbeat interval this.timeout = opts.timeout * 1000 || this.heartbeat * 2; // max heartbeat message timeout this.disconnectOnTimeout = true; } this.timeouts = {}; this.clients = {}; }; module.exports = Command; Command.prototype.handle = function(socket) { if(!this.heartbeat) { // no heartbeat setting return; } var self = this; if(!this.clients[socket.id]) { // clear timers when socket disconnect or error this.clients[socket.id] = 1; socket.once('disconnect', clearTimers.bind(null, this, socket.id)); socket.once('error', clearTimers.bind(null, this, socket.id)); } // clear timeout timer if(self.disconnectOnTimeout) { this.clear(socket.id); } socket.sendRaw(Package.encode(Package.TYPE_HEARTBEAT)); if(self.disconnectOnTimeout) { self.timeouts[socket.id] = setTimeout(function() { logger.info('client %j heartbeat timeout.', socket.id); socket.disconnect(); }, self.timeout); } }; Command.prototype.clear = function(id) { var tid = this.timeouts[id]; if(tid) { clearTimeout(tid); delete this.timeouts[id]; } }; var clearTimers = function(self, id) { delete self.clients[id]; var tid = self.timeouts[id]; if(tid) { clearTimeout(tid); delete self.timeouts[id]; } }; ================================================ FILE: lib/connectors/commands/kick.js ================================================ var Package = require('pomelo-protocol').Package; module.exports.handle = function(socket, reason) { // websocket close code 1000 would emit when client close the connection if(typeof reason === 'string') { var res = { reason: reason }; socket.sendRaw(Package.encode(Package.TYPE_KICK, new Buffer(JSON.stringify(res)))); } }; ================================================ FILE: lib/connectors/common/coder.js ================================================ var Message = require('pomelo-protocol').Message; var Constants = require('../../util/constants'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var encode = function(reqId, route, msg) { if(!!reqId) { return composeResponse(this, reqId, route, msg); } else { return composePush(this, route, msg); } }; var decode = function(msg) { msg = Message.decode(msg.body); var route = msg.route; // decode use dictionary if(!!msg.compressRoute) { if(!!this.connector.useDict) { var abbrs = this.dictionary.getAbbrs(); if(!abbrs[route]) { logger.error('dictionary error! no abbrs for route : %s', route); return null; } route = msg.route = abbrs[route]; } else { logger.error('fail to uncompress route code for msg: %j, server not enable dictionary.', msg); return null; } } // decode use protobuf if(!!this.protobuf && !!this.protobuf.getProtos().client[route]) { msg.body = this.protobuf.decode(route, msg.body); } else if(!!this.decodeIO_protobuf && !!this.decodeIO_protobuf.check(Constants.RESERVED.CLIENT, route)) { msg.body = this.decodeIO_protobuf.decode(route, msg.body); } else { try { msg.body = JSON.parse(msg.body.toString('utf8')); } catch (ex) { msg.body = {}; } } return msg; }; var composeResponse = function(server, msgId, route, msgBody) { if(!msgId || !route || !msgBody) { return null; } msgBody = encodeBody(server, route, msgBody); return Message.encode(msgId, Message.TYPE_RESPONSE, 0, null, msgBody); }; var composePush = function(server, route, msgBody) { if(!route || !msgBody){ return null; } msgBody = encodeBody(server, route, msgBody); // encode use dictionary var compressRoute = 0; if(!!server.dictionary) { var dict = server.dictionary.getDict(); if(!!server.connector.useDict && !!dict[route]) { route = dict[route]; compressRoute = 1; } } return Message.encode(0, Message.TYPE_PUSH, compressRoute, route, msgBody); }; var encodeBody = function(server, route, msgBody) { // encode use protobuf if(!!server.protobuf && !!server.protobuf.getProtos().server[route]) { msgBody = server.protobuf.encode(route, msgBody); } else if(!!server.decodeIO_protobuf && !!server.decodeIO_protobuf.check(Constants.RESERVED.SERVER, route)) { msgBody = server.decodeIO_protobuf.encode(route, msgBody); } else { msgBody = new Buffer(JSON.stringify(msgBody), 'utf8'); } return msgBody; }; module.exports = { encode: encode, decode: decode }; ================================================ FILE: lib/connectors/common/handler.js ================================================ var protocol = require('pomelo-protocol'); var Package = protocol.Package; var logger = require('pomelo-logger').getLogger('pomelo', __filename); var handlers = {}; var ST_INITED = 0; var ST_WAIT_ACK = 1; var ST_WORKING = 2; var ST_CLOSED = 3; var handleHandshake = function(socket, pkg) { if(socket.state !== ST_INITED) { return; } try { socket.emit('handshake', JSON.parse(protocol.strdecode(pkg.body))); } catch (ex) { socket.emit('handshake', {}); } }; var handleHandshakeAck = function(socket, pkg) { if(socket.state !== ST_WAIT_ACK) { return; } socket.state = ST_WORKING; socket.emit('heartbeat'); }; var handleHeartbeat = function(socket, pkg) { if(socket.state !== ST_WORKING) { return; } socket.emit('heartbeat'); }; var handleData = function(socket, pkg) { if(socket.state !== ST_WORKING) { return; } socket.emit('message', pkg); }; handlers[Package.TYPE_HANDSHAKE] = handleHandshake; handlers[Package.TYPE_HANDSHAKE_ACK] = handleHandshakeAck; handlers[Package.TYPE_HEARTBEAT] = handleHeartbeat; handlers[Package.TYPE_DATA] = handleData; var handle = function(socket, pkg) { var handler = handlers[pkg.type]; if(!!handler) { handler(socket, pkg); } else { logger.error('could not find handle invalid data package.'); socket.disconnect(); } }; module.exports = handle; ================================================ FILE: lib/connectors/hybrid/switcher.js ================================================ var EventEmitter = require('events').EventEmitter; var util = require('util'); var WSProcessor = require('./wsprocessor'); var TCPProcessor = require('./tcpprocessor'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var HTTP_METHODS = [ 'GET', 'POST', 'DELETE', 'PUT', 'HEAD' ]; var ST_STARTED = 1; var ST_CLOSED = 2; var DEFAULT_TIMEOUT = 90; /** * Switcher for tcp and websocket protocol * * @param {Object} server tcp server instance from node.js net module */ var Switcher = function(server, opts) { EventEmitter.call(this); this.server = server; this.wsprocessor = new WSProcessor(); this.tcpprocessor = new TCPProcessor(opts.closeMethod); this.id = 1; this.timeout = (opts.timeout || DEFAULT_TIMEOUT) * 1000; this.setNoDelay = opts.setNoDelay; if (!opts.ssl) { this.server.on('connection', this.newSocket.bind(this)); } else { this.server.on('secureConnection', this.newSocket.bind(this)); this.server.on('clientError', function(e, tlsSo) { logger.warn('an ssl error occured before handshake established: ', e); tlsSo.destroy(); }); } this.wsprocessor.on('connection', this.emit.bind(this, 'connection')); this.tcpprocessor.on('connection', this.emit.bind(this, 'connection')); this.state = ST_STARTED; }; util.inherits(Switcher, EventEmitter); module.exports = Switcher; Switcher.prototype.newSocket = function(socket) { if(this.state !== ST_STARTED) { return; } socket.setTimeout(this.timeout, function() { logger.warn('connection is timeout without communication, the remote ip is %s && port is %s', socket.remoteAddress, socket.remotePort); socket.destroy(); }); var self = this; socket.once('data', function(data) { // FIXME: handle incomplete HTTP method if(isHttp(data)) { processHttp(self, self.wsprocessor, socket, data); } else { if(!!self.setNoDelay) { socket.setNoDelay(true); } processTcp(self, self.tcpprocessor, socket, data); } }); }; Switcher.prototype.close = function() { if(this.state !== ST_STARTED) { return; } this.state = ST_CLOSED; this.wsprocessor.close(); this.tcpprocessor.close(); }; var isHttp = function(data) { var head = data.toString('utf8', 0, 4); for(var i=0, l=HTTP_METHODS.length; i 2) return null; if (typeof id !== 'number' || id < 0 || id > 0xFFFF) return null; /* Generate header */ packet.header = protocol.codes.publish << protocol.CMD_SHIFT | dup | qos << protocol.QOS_SHIFT | retain; /* Topic name */ packet.payload = packet.payload.concat(gen_string(topic)); /* Message ID */ if (qos > 0) packet.payload = packet.payload.concat(gen_number(id)); var buf = new Buffer([packet.header] .concat(gen_length(packet.payload.length + payload.length)) .concat(packet.payload)); return Buffer.concat([buf, payload]); }; /* Requires length be a number > 0 */ var gen_length = function(length) { if(typeof length !== "number") return null; if(length < 0) return null; var len = []; var digit = 0; do { digit = length % 128 | 0; length = length / 128 | 0; if (length > 0) { digit = digit | 0x80; } len.push(digit); } while (length > 0); return len; }; var gen_string = function(str, without_length) { /* based on code in (from http://farhadi.ir/downloads/utf8.js) */ if(arguments.length < 2) without_length = false; if(typeof str !== "string") return null; if(typeof without_length !== "boolean") return null; var string = []; var length = 0; for(var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); if (code < 128) { string.push(code); ++length; } else if (code < 2048) { string.push(192 + ((code >> 6 ) )); ++length; string.push(128 + ((code ) & 63)); ++length; } else if (code < 65536) { string.push(224 + ((code >> 12) )); ++length; string.push(128 + ((code >> 6 ) & 63)); ++length; string.push(128 + ((code ) & 63)); ++length; } else if (code < 2097152) { string.push(240 + ((code >> 18) )); ++length; string.push(128 + ((code >> 12) & 63)); ++length; string.push(128 + ((code >> 6 ) & 63)); ++length; string.push(128 + ((code ) & 63)); ++length; } else { throw new Error("Can't encode character with code " + code); } } return without_length ? string : gen_number(length).concat(string); }; var gen_number = function(num) { var number = [num >> 8, num & 0x00FF]; return number; }; var randint = function() { return Math.floor(Math.random() * 0xFFFF); }; ================================================ FILE: lib/connectors/mqtt/mqttadaptor.js ================================================ var Adaptor = function(opts) { opts = opts || {}; this.subReqs = {}; this.publishRoute = opts.publishRoute; this.subscribeRoute = opts.subscribeRoute; }; module.exports = Adaptor; Adaptor.prototype.onPublish = function(client, packet) { var route = this.publishRoute; if(!route) { throw new Error('unspecified publish route.'); } var payload = packet.payload; if(payload instanceof Buffer) { payload = payload.toString('utf8'); } var req = { id: packet.messageId, route: route, body: packet }; client.emit('message', req); if(packet.qos === 1) { client.socket.puback({messageId: packet.messageId}); } }; Adaptor.prototype.onSubscribe = function(client, packet) { var route = this.subscribeRoute; if(!route) { throw new Error('unspecified subscribe route.'); } var req = { id: packet.messageId, route: route, body: { subscriptions: packet.subscriptions } }; this.subReqs[packet.messageId] = packet; client.emit('message', req); }; Adaptor.prototype.onPubAck = function(client, packet) { var req = { id: packet.messageId, route: 'connector.mqttHandler.pubAck', body: { mid: packet.messageId } }; this.subReqs[packet.messageId] = packet; client.emit('message', req); }; /** * Publish message or subscription ack. * * if packet.id exist and this.subReqs[packet.id] exist then packet is a suback. * Subscription is request/response mode. * packet.id is pass from client in packet.messageId and record in Pomelo context and attached to the subscribe response packet. * packet.body is the context that returned by subscribe next callback. * * if packet.id not exist then packet is a publish message. * * otherwise packet is a illegal packet. */ Adaptor.prototype.publish = function(client, packet) { var mid = packet.id; var subreq = this.subReqs[mid]; if(subreq) { // is suback client.socket.suback({messageId: mid, granted: packet.body}); delete this.subReqs[mid]; return; } client.socket.publish(packet.body); }; ================================================ FILE: lib/connectors/mqtt/protocol.js ================================================ /* Protocol - protocol constants */ /* Command code => mnemonic */ module.exports.types = { 0: 'reserved', 1: 'connect', 2: 'connack', 3: 'publish', 4: 'puback', 5: 'pubrec', 6: 'pubrel', 7: 'pubcomp', 8: 'subscribe', 9: 'suback', 10: 'unsubscribe', 11: 'unsuback', 12: 'pingreq', 13: 'pingresp', 14: 'disconnect', 15: 'reserved' }; /* Mnemonic => Command code */ module.exports.codes = {}; for(var k in module.exports.types) { var v = module.exports.types[k]; module.exports.codes[v] = k; } /* Header */ module.exports.CMD_SHIFT = 4; module.exports.CMD_MASK = 0xF0; module.exports.DUP_MASK = 0x08; module.exports.QOS_MASK = 0x03; module.exports.QOS_SHIFT = 1; module.exports.RETAIN_MASK = 0x01; /* Length */ module.exports.LENGTH_MASK = 0x7F; module.exports.LENGTH_FIN_MASK = 0x80; /* Connect */ module.exports.USERNAME_MASK = 0x80; module.exports.PASSWORD_MASK = 0x40; module.exports.WILL_RETAIN_MASK = 0x20; module.exports.WILL_QOS_MASK = 0x18; module.exports.WILL_QOS_SHIFT = 3; module.exports.WILL_FLAG_MASK = 0x04; module.exports.CLEAN_SESSION_MASK = 0x02; ================================================ FILE: lib/connectors/mqttconnector.js ================================================ var util = require('util'); var EventEmitter = require('events').EventEmitter; var mqtt = require('mqtt'); var constants = require('../util/constants'); var MQTTSocket = require('./mqttsocket'); var Adaptor = require('./mqtt/mqttadaptor'); var generate = require('./mqtt/generate'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var curId = 1; /** * Connector that manager low level connection and protocol bewteen server and client. * Develper can provide their own connector to switch the low level prototol, such as tcp or probuf. */ var Connector = function(port, host, opts) { if (!(this instanceof Connector)) { return new Connector(port, host, opts); } EventEmitter.call(this); this.port = port; this.host = host; this.opts = opts || {}; this.adaptor = new Adaptor(this.opts); }; util.inherits(Connector, EventEmitter); module.exports = Connector; /** * Start connector to listen the specified port */ Connector.prototype.start = function(cb) { var self = this; this.mqttServer = mqtt.createServer(); this.mqttServer.on('client', function(client) { client.on('error', function(err) { client.stream.destroy(); }); client.on('close', function() { client.stream.destroy(); }); client.on('disconnect', function(packet) { client.stream.destroy(); }); if(self.opts.disconnectOnTimeout) { var timeout = self.opts.timeout * 1000 || constants.TIME.DEFAULT_MQTT_HEARTBEAT_TIMEOUT; client.stream.setTimeout(timeout,function() { client.emit('close'); }); } client.on('connect', function(packet) { client.connack({returnCode: 0}); var mqttsocket = new MQTTSocket(curId++, client, self.adaptor); self.emit('connection', mqttsocket); }); }); this.mqttServer.listen(this.port); process.nextTick(cb); }; Connector.prototype.stop = function() { this.mqttServer.close(); process.exit(0); }; var composeResponse = function(msgId, route, msgBody) { return { id: msgId, body: msgBody }; }; var composePush = function(route, msgBody) { var msg = generate.publish(msgBody); if(!msg) { logger.error('invalid mqtt publish message: %j', msgBody); } return msg; }; Connector.prototype.encode = function(reqId, route, msgBody) { if (!!reqId) { return composeResponse(reqId, route, msgBody); } else { return composePush(route, msgBody); } }; Connector.prototype.close = function() { this.mqttServer.close(); }; ================================================ FILE: lib/connectors/mqttsocket.js ================================================ var util = require('util'); var EventEmitter = require('events').EventEmitter; var ST_INITED = 1; var ST_CLOSED = 2; /** * Socket class that wraps socket and websocket to provide unified interface for up level. */ var Socket = function(id, socket, adaptor) { EventEmitter.call(this); this.id = id; this.socket = socket; this.remoteAddress = { ip: socket.stream.remoteAddress, port: socket.stream.remotePort }; this.adaptor = adaptor; var self = this; socket.on('close', this.emit.bind(this, 'disconnect')); socket.on('error', this.emit.bind(this, 'disconnect')); socket.on('disconnect', this.emit.bind(this, 'disconnect')); socket.on('pingreq', function(packet) { socket.pingresp(); }); socket.on('subscribe', this.adaptor.onSubscribe.bind(this.adaptor, this)); socket.on('publish', this.adaptor.onPublish.bind(this.adaptor, this)); this.state = ST_INITED; // TODO: any other events? }; util.inherits(Socket, EventEmitter); module.exports = Socket; Socket.prototype.send = function(msg) { if(this.state !== ST_INITED) { return; } if(msg instanceof Buffer) { // if encoded, send directly this.socket.stream.write(msg); } else { this.adaptor.publish(this, msg); } }; Socket.prototype.sendBatch = function(msgs) { for(var i = 0, l = msgs.length; i 0) { res <<= 8; } res |= str.charCodeAt(offset + i) & 0xff; } return res; }; ================================================ FILE: lib/connectors/siosocket.js ================================================ var util = require('util'); var EventEmitter = require('events').EventEmitter; var ST_INITED = 0; var ST_CLOSED = 1; /** * Socket class that wraps socket.io socket to provide unified interface for up level. */ var Socket = function(id, socket) { EventEmitter.call(this); this.id = id; this.socket = socket; this.remoteAddress = { ip: socket.handshake.address.address, port: socket.handshake.address.port }; var self = this; socket.on('disconnect', this.emit.bind(this, 'disconnect')); socket.on('error', this.emit.bind(this, 'error')); socket.on('message', function(msg) { self.emit('message', msg); }); this.state = ST_INITED; // TODO: any other events? }; util.inherits(Socket, EventEmitter); module.exports = Socket; Socket.prototype.send = function(msg) { if(this.state !== ST_INITED) { return; } if(typeof msg !== 'string') { msg = JSON.stringify(msg); } this.socket.send(msg); }; Socket.prototype.disconnect = function() { if(this.state === ST_CLOSED) { return; } this.state = ST_CLOSED; this.socket.disconnect(); }; Socket.prototype.sendBatch = function(msgs) { this.send(encodeBatch(msgs)); }; /** * Encode batch msg to client */ var encodeBatch = function(msgs){ var res = '[', msg; for(var i=0, l=msgs.length; i 0) { res += ','; } msg = msgs[i]; if(typeof msg === 'string') { res += msg; } else { res += JSON.stringify(msg); } } res += ']'; return res; }; ================================================ FILE: lib/connectors/udpconnector.js ================================================ var net = require('net'); var util = require('util'); var dgram = require("dgram"); var utils = require('../util/utils'); var Constants = require('../util/constants'); var UdpSocket = require('./udpsocket'); var Kick = require('./commands/kick'); var Handshake = require('./commands/handshake'); var Heartbeat = require('./commands/heartbeat'); var protocol = require('pomelo-protocol'); var Package = protocol.Package; var Message = protocol.Message; var coder = require('./common/coder'); var EventEmitter = require('events').EventEmitter; var curId = 1; var Connector = function(port, host, opts) { if (!(this instanceof Connector)) { return new Connector(port, host, opts); } EventEmitter.call(this); this.opts = opts || {}; this.type = opts.udpType || 'udp4'; this.handshake = new Handshake(opts); if(!opts.heartbeat) { opts.heartbeat = Constants.TIME.DEFAULT_UDP_HEARTBEAT_TIME; opts.timeout = Constants.TIME.DEFAULT_UDP_HEARTBEAT_TIMEOUT; } this.heartbeat = new Heartbeat(utils.extends(opts, {disconnectOnTimeout: true})); this.clients = {}; this.host = host; this.port = port; }; util.inherits(Connector, EventEmitter); module.exports = Connector; Connector.prototype.start = function(cb) { var self = this; this.tcpServer = net.createServer(); this.socket = dgram.createSocket(this.type, function(msg, peer) { var key = genKey(peer); if(!self.clients[key]) { var udpsocket = new UdpSocket(curId++, self.socket, peer); self.clients[key] = udpsocket; udpsocket.on('handshake', self.handshake.handle.bind(self.handshake, udpsocket)); udpsocket.on('heartbeat', self.heartbeat.handle.bind(self.heartbeat, udpsocket)); udpsocket.on('disconnect', self.heartbeat.clear.bind(self.heartbeat, udpsocket.id)); udpsocket.on('disconnect', function() { delete self.clients[genKey(udpsocket.peer)]; }); udpsocket.on('closing', Kick.handle.bind(null, udpsocket)); self.emit('connection', udpsocket); } }); this.socket.on('message', function(data, peer) { var socket = self.clients[genKey(peer)]; if(!!socket) { socket.emit('package', data); } }); this.socket.on('error', function(err) { logger.error('udp socket encounters with error: %j', err.stack); return; }); this.socket.bind(this.port, this.host); this.tcpServer.listen(this.port); process.nextTick(cb); }; Connector.decode = Connector.prototype.decode = coder.decode; Connector.encode = Connector.prototype.encode = coder.encode; Connector.prototype.stop = function(force, cb) { this.socket.close(); process.nextTick(cb); }; var genKey = function(peer) { return peer.address + ":" + peer.port; }; ================================================ FILE: lib/connectors/udpsocket.js ================================================ var util = require('util'); var handler = require('./common/handler'); var protocol = require('pomelo-protocol'); var Package = protocol.Package; var EventEmitter = require('events').EventEmitter; var logger = require('pomelo-logger').getLogger('pomelo', __filename); var ST_INITED = 0; var ST_WAIT_ACK = 1; var ST_WORKING = 2; var ST_CLOSED = 3; var Socket = function(id, socket, peer) { EventEmitter.call(this); this.id = id; this.socket = socket; this.peer = peer; this.host = peer.address; this.port = peer.port; this.remoteAddress = { ip: this.host, port: this.port }; var self = this; this.on('package', function(pkg) { if(!!pkg) { pkg = Package.decode(pkg); handler(self, pkg); } }); this.state = ST_INITED; }; util.inherits(Socket, EventEmitter); module.exports = Socket; /** * Send byte data package to client. * * @param {Buffer} msg byte data */ Socket.prototype.send = function(msg) { if(this.state !== ST_WORKING) { return; } if(msg instanceof String) { msg = new Buffer(msg); } else if(!(msg instanceof Buffer)) { msg = new Buffer(JSON.stringify(msg)); } this.sendRaw(Package.encode(Package.TYPE_DATA, msg)); }; Socket.prototype.sendRaw = function(msg) { this.socket.send(msg, 0, msg.length, this.port, this.host, function(err, bytes) { if(!!err) { logger.error('send msg to remote with err: %j', err.stack); return; } }); }; Socket.prototype.sendForce = function(msg) { if(this.state === ST_CLOSED) { return; } this.sendRaw(msg); }; Socket.prototype.handshakeResponse = function(resp) { if(this.state !== ST_INITED) { return; } this.sendRaw(resp); this.state = ST_WAIT_ACK; }; Socket.prototype.sendBatch = function(msgs) { if(this.state !== ST_WORKING) { return; } var rs = []; for(var i=0; i this.maxSize) { logger.warn('timeout filter is out of range, current size is %s, max size is %s', count, this.maxSize); next(); return; } this.curId++; this.timeouts[this.curId] = setTimeout(function() { logger.error('request %j timeout.', msg.__route__); }, this.timeout); session.__timeout__ = this.curId; next(); }; Filter.prototype.after = function(err, msg, session, resp, next) { var timeout = this.timeouts[session.__timeout__]; if(timeout) { clearTimeout(timeout); delete this.timeouts[session.__timeout__]; } next(err); }; ================================================ FILE: lib/filters/handler/toobusy.js ================================================ /** * Filter for toobusy. * if the process is toobusy, just skip the new request */ var conLogger = require('pomelo-logger').getLogger('con-log', __filename); var toobusy = null; var DEFAULT_MAXLAG = 70; module.exports = function(maxLag) { return new Filter(maxLag || DEFAULT_MAXLAG); }; var Filter = function(maxLag) { try { toobusy = require('toobusy'); } catch(e) { } if(!!toobusy) { toobusy.maxLag(maxLag); } }; Filter.prototype.before = function(msg, session, next) { if (!!toobusy && toobusy()) { conLogger.warn('[toobusy] reject request msg: ' + msg); var err = new Error('Server toobusy!'); err.code = 500; next(err); } else { next(); } }; ================================================ FILE: lib/filters/rpc/rpcLog.js ================================================ /** * Filter for rpc log. * Record used time for remote process call. */ var rpcLogger = require('pomelo-logger').getLogger('rpc-log', __filename); var utils = require('../../util/utils'); module.exports = function() { return new Filter(); }; var Filter = function () { }; Filter.prototype.name = 'rpcLog'; /** * Before filter for rpc */ Filter.prototype.before = function(serverId, msg, opts, next) { opts = opts||{}; opts.__start_time__ = Date.now(); next(); }; /** * After filter for rpc */ Filter.prototype.after = function(serverId, msg, opts, next) { if(!!opts && !!opts.__start_time__) { var start = opts.__start_time__; var end = Date.now(); var timeUsed = end - start; var log = { route: msg.service, args: msg.args, time: utils.format(new Date(start)), timeUsed: timeUsed }; rpcLogger.info(JSON.stringify(log)); } next(); }; ================================================ FILE: lib/filters/rpc/toobusy.js ================================================ /** * Filter for rpc log. * Reject rpc request when toobusy */ var rpcLogger = require('pomelo-logger').getLogger('rpc-log', __filename); var toobusy = null; var DEFAULT_MAXLAG = 70; module.exports = function(maxLag) { return new Filter(maxLag || DEFAULT_MAXLAG); }; var Filter = function(maxLag) { try { toobusy = require('toobusy'); } catch(e) { } if(!!toobusy) { toobusy.maxLag(maxLag); } }; Filter.prototype.name = 'toobusy'; /** * Before filter for rpc */ Filter.prototype.before = function(serverId, msg, opts, next) { opts = opts||{}; if (!!toobusy && toobusy()) { rpcLogger.warn('Server too busy for rpc request, serverId:' + serverId + ' msg: ' + msg); var err = new Error('Backend server ' + serverId + ' is too busy now!'); err.code = 500; next(err); } else { next(); } }; ================================================ FILE: lib/index.js ================================================ module.exports = require('./pomelo'); ================================================ FILE: lib/master/master.js ================================================ var starter = require('./starter'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var crashLogger = require('pomelo-logger').getLogger('crash-log', __filename); var adminLogger = require('pomelo-logger').getLogger('admin-log', __filename); var admin = require('pomelo-admin'); var util = require('util'); var utils = require('../util/utils'); var moduleUtil = require('../util/moduleUtil'); var Constants = require('../util/constants'); var Server = function(app, opts) { this.app = app; this.masterInfo = app.getMaster(); this.registered = {}; this.modules = []; opts = opts || {}; opts.port = this.masterInfo.port; opts.env = this.app.get(Constants.RESERVED.ENV); this.closeWatcher = opts.closeWatcher; this.masterConsole = admin.createMasterConsole(opts); }; module.exports = Server; Server.prototype.start = function(cb) { moduleUtil.registerDefaultModules(true, this.app, this.closeWatcher); moduleUtil.loadModules(this, this.masterConsole); var self = this; // start master console this.masterConsole.start(function(err) { if(err) { process.exit(0); } moduleUtil.startModules(self.modules, function(err) { if(err) { utils.invokeCallback(cb, err); return; } if(self.app.get(Constants.RESERVED.MODE) !== Constants.RESERVED.STAND_ALONE) { starter.runServers(self.app); } utils.invokeCallback(cb); }); }); this.masterConsole.on('error', function(err) { if(!!err) { logger.error('masterConsole encounters with error: ' + err.stack); return; } }); this.masterConsole.on('reconnect', function(info){ self.app.addServers([info]); }); // monitor servers disconnect event this.masterConsole.on('disconnect', function(id, type, info, reason) { crashLogger.info(util.format('[%s],[%s],[%s],[%s]', type, id, Date.now(), reason || 'disconnect')); var count = 0; var time = 0; var pingTimer = null; var server = self.app.getServerById(id); var stopFlags = self.app.get(Constants.RESERVED.STOP_SERVERS) || []; if(!!server && (server[Constants.RESERVED.AUTO_RESTART] === 'true' || server[Constants.RESERVED.RESTART_FORCE] === 'true') && stopFlags.indexOf(id) < 0) { var setTimer = function(time) { pingTimer = setTimeout(function() { utils.ping(server.host, function(flag) { if(flag) { handle(); } else { count++; if(count > 3) { time = Constants.TIME.TIME_WAIT_MAX_PING; } else { time = Constants.TIME.TIME_WAIT_PING * count; } setTimer(time); } }); }, time); }; setTimer(time); var handle = function() { clearTimeout(pingTimer); utils.checkPort(server, function(status) { if(status === 'error') { utils.invokeCallback(cb, new Error('Check port command executed with error.')); return; } else if(status === 'busy') { if(!!server[Constants.RESERVED.RESTART_FORCE]) { starter.kill([info.pid], [server]); } else { utils.invokeCallback(cb, new Error('Port occupied already, check your server to add.')); return; } } setTimeout(function() { starter.run(self.app, server, null); }, Constants.TIME.TIME_WAIT_STOP); }); }; } }); // monitor servers register event this.masterConsole.on('register', function(record) { starter.bindCpu(record.id, record.pid, record.host); }); this.masterConsole.on('admin-log', function(log, error) { if(error) { adminLogger.error(JSON.stringify(log)); } else { adminLogger.info(JSON.stringify(log)); } }); }; Server.prototype.stop = function(cb) { this.masterConsole.stop(); process.nextTick(cb); }; ================================================ FILE: lib/master/starter.js ================================================ var cp = require('child_process'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var starter = module.exports; var util = require('util'); var utils = require('../util/utils'); var Constants = require('../util/constants'); var env = Constants.RESERVED.ENV_DEV; var os=require('os'); var cpus = {}; var pomelo = require('../pomelo'); /** * Run all servers * * @param {Object} app current application context * @return {Void} */ starter.runServers = function(app) { var server, servers; var condition = app.startId || app.type; switch(condition) { case Constants.RESERVED.MASTER: break; case Constants.RESERVED.ALL: servers = app.getServersFromConfig(); for (var serverId in servers) { this.run(app, servers[serverId]); } break; default: server = app.getServerFromConfig(condition); if(!!server) { this.run(app, server); } else { servers = app.get(Constants.RESERVED.SERVERS)[condition]; for(var i=0; i * MIT Licensed */ var logger = require('pomelo-logger').getLogger('pomelo', __filename); var countDownLatch = require('../util/countDownLatch'); var utils = require('../util/utils'); var Constants = require('../util/constants'); var starter = require('../master/starter'); var exec = require('child_process').exec; module.exports = function(opts) { return new Module(opts); }; module.exports.moduleId = '__console__'; var Module = function(opts) { opts = opts || {}; this.app = opts.app; this.starter = opts.starter; }; Module.prototype.monitorHandler = function(agent, msg, cb) { var serverId = agent.id; switch(msg.signal) { case 'stop': if(agent.type === Constants.RESERVED.MASTER) { return; } this.app.stop(true); break; case 'list': var serverType = agent.type; var pid = process.pid; var heapUsed = (process.memoryUsage().heapUsed/(1024 * 1024)).toFixed(2); var rss = (process.memoryUsage().rss/(1024 * 1024)).toFixed(2); var heapTotal = (process.memoryUsage().heapTotal/(1024 * 1024)).toFixed(2); var uptime = (process.uptime()/60).toFixed(2); utils.invokeCallback(cb, { serverId: serverId, body: {serverId:serverId, serverType: serverType, pid:pid, rss: rss, heapTotal: heapTotal, heapUsed:heapUsed, uptime:uptime} }); break; case 'kill': utils.invokeCallback(cb, serverId); if (agent.type !== 'master') { setTimeout(function() { process.exit(-1); }, Constants.TIME.TIME_WAIT_MONITOR_KILL); } break; case 'addCron': this.app.addCrons([msg.cron]); break; case 'removeCron': this.app.removeCrons([msg.cron]); break; case 'blacklist': if(this.app.isFrontend()) { var connector = this.app.components.__connector__; connector.blacklist = connector.blacklist.concat(msg.blacklist); } break; case 'restart': if(agent.type === Constants.RESERVED.MASTER) { return; } var self = this; var server = this.app.get(Constants.RESERVED.CURRENT_SERVER); utils.invokeCallback(cb, server); process.nextTick(function() { self.app.stop(true); }); break; default: logger.error('receive error signal: %j', msg); break; } }; Module.prototype.clientHandler = function(agent, msg, cb) { var app = this.app; switch(msg.signal) { case 'kill': kill(app, agent, msg, cb); break; case 'stop': stop(app, agent, msg, cb); break; case 'list': list(agent, msg, cb); break; case 'add': add(app, msg, cb); break; case 'addCron': addCron(app, agent, msg, cb); break; case 'removeCron': removeCron(app, agent, msg, cb); break; case 'blacklist': blacklist(agent, msg, cb); break; case 'restart': restart(app, agent, msg, cb); break; default: utils.invokeCallback(cb, new Error('The command cannot be recognized, please check.'), null); break; } }; var kill = function(app, agent, msg, cb) { var sid, record; var serverIds = []; var count = utils.size(agent.idMap); var latch = countDownLatch.createCountDownLatch(count, {timeout: Constants.TIME.TIME_WAIT_MASTER_KILL}, function(isTimeout) { if (!isTimeout) { utils.invokeCallback(cb, null, {code: 'ok'}); } else { utils.invokeCallback(cb, null, {code: 'remained', serverIds: serverIds}); } setTimeout(function() { process.exit(-1); }, Constants.TIME.TIME_WAIT_MONITOR_KILL); }); var agentRequestCallback = function(msg) { for (var i = 0; i < serverIds.length; ++i) { if (serverIds[i] === msg) { serverIds.splice(i,1); latch.done(); break; } } }; for(sid in agent.idMap) { record = agent.idMap[sid]; serverIds.push(record.id); agent.request(record.id, module.exports.moduleId, { signal: msg.signal }, agentRequestCallback); } }; var stop = function(app, agent, msg, cb) { var serverIds = msg.ids; if(!!serverIds.length) { var servers = app.getServers(); app.set(Constants.RESERVED.STOP_SERVERS, serverIds); for(var i=0; i * MIT Licensed */ /** * Module dependencies. */ var fs = require('fs'); var path = require('path'); var application = require('./application'); var Package = require('../package'); /** * Expose `createApplication()`. * * @module */ var Pomelo = module.exports = {}; /** * Framework version. */ Pomelo.version = Package.version; /** * Event definitions that would be emitted by app.event */ Pomelo.events = require('./util/events'); /** * auto loaded components */ Pomelo.components = {}; /** * auto loaded filters */ Pomelo.filters = {}; /** * auto loaded rpc filters */ Pomelo.rpcFilters = {}; /** * connectors */ Pomelo.connectors = {}; Pomelo.connectors.__defineGetter__('sioconnector', load.bind(null, './connectors/sioconnector')); Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector')); Pomelo.connectors.__defineGetter__('udpconnector', load.bind(null, './connectors/udpconnector')); Pomelo.connectors.__defineGetter__('mqttconnector', load.bind(null, './connectors/mqttconnector')); /** * pushSchedulers */ Pomelo.pushSchedulers = {}; Pomelo.pushSchedulers.__defineGetter__('direct', load.bind(null, './pushSchedulers/direct')); Pomelo.pushSchedulers.__defineGetter__('buffer', load.bind(null, './pushSchedulers/buffer')); var self = this; /** * Create an pomelo application. * * @return {Application} * @memberOf Pomelo * @api public */ Pomelo.createApp = function (opts) { var app = application; app.init(opts); self.app = app; return app; }; /** * Get application */ Object.defineProperty(Pomelo, 'app', { get:function () { return self.app; } }); /** * Auto-load bundled components with getters. */ fs.readdirSync(__dirname + '/components').forEach(function (filename) { if (!/\.js$/.test(filename)) { return; } var name = path.basename(filename, '.js'); var _load = load.bind(null, './components/', name); Pomelo.components.__defineGetter__(name, _load); Pomelo.__defineGetter__(name, _load); }); fs.readdirSync(__dirname + '/filters/handler').forEach(function (filename) { if (!/\.js$/.test(filename)) { return; } var name = path.basename(filename, '.js'); var _load = load.bind(null, './filters/handler/', name); Pomelo.filters.__defineGetter__(name, _load); Pomelo.__defineGetter__(name, _load); }); fs.readdirSync(__dirname + '/filters/rpc').forEach(function (filename) { if (!/\.js$/.test(filename)) { return; } var name = path.basename(filename, '.js'); var _load = load.bind(null, './filters/rpc/', name); Pomelo.rpcFilters.__defineGetter__(name, _load); }); function load(path, name) { if (name) { return require(path + name); } return require(path); } ================================================ FILE: lib/pushSchedulers/buffer.js ================================================ var utils = require('../util/utils'); var DEFAULT_FLUSH_INTERVAL = 20; var Service = function(app, opts) { if (!(this instanceof Service)) { return new Service(app, opts); } opts = opts || {}; this.app = app; this.flushInterval = opts.flushInterval || DEFAULT_FLUSH_INTERVAL; this.sessions = {}; // sid -> msg queue this.tid = null; }; module.exports = Service; Service.prototype.start = function(cb) { this.tid = setInterval(flush.bind(null, this), this.flushInterval); process.nextTick(function() { utils.invokeCallback(cb); }); }; Service.prototype.stop = function(force, cb) { if(this.tid) { clearInterval(this.tid); this.tid = null; } process.nextTick(function() { utils.invokeCallback(cb); }); }; Service.prototype.schedule = function(reqId, route, msg, recvs, opts, cb) { opts = opts || {}; if(opts.type === 'broadcast') { doBroadcast(this, msg, opts.userOptions); } else { doBatchPush(this, msg, recvs); } process.nextTick(function() { utils.invokeCallback(cb); }); }; var doBroadcast = function(self, msg, opts) { var channelService = self.app.get('channelService'); var sessionService = self.app.get('sessionService'); if(opts.binded) { sessionService.forEachBindedSession(function(session) { if(channelService.broadcastFilter && !channelService.broadcastFilter(session, msg, opts.filterParam)) { return; } enqueue(self, session, msg); }); } else { sessionService.forEachSession(function(session) { if(channelService.broadcastFilter && !channelService.broadcastFilter(session, msg, opts.filterParam)) { return; } enqueue(self, session, msg); }); } }; var doBatchPush = function(self, msg, recvs) { var sessionService = self.app.get('sessionService'); var session; for(var i=0, l=recvs.length; i ST_INITED) { return; } this.globalFilterService = initFilter(true, this.app); this.filterService = initFilter(false, this.app); this.handlerService = initHandler(this.app, this.opts); this.cronHandlers = loadCronHandlers(this.app); loadCrons(this, this.app); this.state = ST_STARTED; }; pro.afterStart = function() { scheduleCrons(this, this.crons); }; /** * Stop server */ pro.stop = function() { this.state = ST_STOPED; }; /** * Global handler. * * @param {Object} msg request message * @param {Object} session session object * @param {Callback} callback function */ pro.globalHandle = function(msg, session, cb) { if(this.state !== ST_STARTED) { utils.invokeCallback(cb, new Error('server not started')); return; } var routeRecord = parseRoute(msg.route); if(!routeRecord) { utils.invokeCallback(cb, new Error('meet unknown route message %j', msg.route)); return; } if (routeRecord.method === 'constructor') { logger.warn('attack session:', session, msg); this.app.sessionService.kickBySessionId(session.id, 'attack'); return; } var self = this; var dispatch = function(err, resp, opts) { if(err) { handleError(true, self, err, msg, session, resp, opts, function(err, resp, opts) { response(true, self, err, msg, session, resp, opts, cb); }); return; } if(self.app.getServerType() !== routeRecord.serverType) { doForward(self.app, msg, session, routeRecord, function(err, resp, opts) { response(true, self, err, msg, session, resp, opts, cb); }); } else { doHandle(self, msg, session, routeRecord, function(err, resp, opts) { response(true, self, err, msg, session, resp, opts, cb); }); } }; beforeFilter(true, self, msg, session, dispatch); }; /** * Handle request */ pro.handle = function(msg, session, cb) { if(this.state !== ST_STARTED) { cb(new Error('server not started')); return; } var routeRecord = parseRoute(msg.route); doHandle(this, msg, session, routeRecord, cb); }; /** * Add crons at runtime. * * @param {Array} crons would be added in application */ pro.addCrons = function(crons) { this.cronHandlers = loadCronHandlers(this.app); for(var i=0, l=crons.length; i= comps.length) { utils.invokeCallback(cb); return; } var comp = comps[index]; if (typeof comp.stop === 'function') { comp.stop(force, function() { // ignore any error module.exports.stopComps(comps, index + 1, force, cb); }); } else { module.exports.stopComps(comps, index + 1, force, cb); } }; /** * Apply command to loaded components. * This method would invoke the component {method} in series. * Any component {method} return err, it would return err directly. * * @param {Array} comps loaded component list * @param {String} method component lifecycle method name, such as: start, stop * @param {Function} cb */ module.exports.optComponents = function(comps, method, cb) { var i = 0; async.forEachSeries(comps, function(comp, done) { i++; if (typeof comp[method] === 'function') { comp[method](done); } else { done(); } }, function(err) { if (err) { if(typeof err === 'string') { logger.error('fail to operate component, method: %s, err: %j', method, err); } else { logger.error('fail to operate component, method: %s, err: %j', method, err.stack); } } utils.invokeCallback(cb, err); }); }; /** * Load server info from config/servers.json. */ var loadServers = function(app) { app.loadConfigBaseApp(Constants.RESERVED.SERVERS, Constants.FILEPATH.SERVER); var servers = app.get(Constants.RESERVED.SERVERS); var serverMap = {}, slist, i, l, server; for (var serverType in servers) { slist = servers[serverType]; for (i = 0, l = slist.length; i < l; i++) { server = slist[i]; server.serverType = serverType; if(server[Constants.RESERVED.CLUSTER_COUNT]) { utils.loadCluster(app, server, serverMap); continue; } serverMap[server.id] = server; if (server.wsPort) { logger.warn('wsPort is deprecated, use clientPort in frontend server instead, server: %j', server); } } } app.set(Constants.KEYWORDS.SERVER_MAP, serverMap); }; /** * Load master info from config/master.json. */ var loadMaster = function(app) { app.loadConfigBaseApp(Constants.RESERVED.MASTER, Constants.FILEPATH.MASTER); app.master = app.get(Constants.RESERVED.MASTER); }; /** * Process server start command */ var processArgs = function(app, args) { var serverType = args.serverType || Constants.RESERVED.MASTER; var serverId = args.id || app.getMaster().id; var mode = args.mode || Constants.RESERVED.CLUSTER; var masterha = args.masterha || 'false'; var type = args.type || Constants.RESERVED.ALL; var startId = args.startId; app.set(Constants.RESERVED.MAIN, args.main, true); app.set(Constants.RESERVED.SERVER_TYPE, serverType, true); app.set(Constants.RESERVED.SERVER_ID, serverId, true); app.set(Constants.RESERVED.MODE, mode, true); app.set(Constants.RESERVED.TYPE, type, true); if(!!startId) { app.set(Constants.RESERVED.STARTID, startId, true); } if (masterha === 'true') { app.master = args; app.set(Constants.RESERVED.CURRENT_SERVER, args, true); } else if (serverType !== Constants.RESERVED.MASTER) { app.set(Constants.RESERVED.CURRENT_SERVER, args, true); } else { app.set(Constants.RESERVED.CURRENT_SERVER, app.getMaster(), true); } }; /** * Setup enviroment. */ var setupEnv = function(app, args) { app.set(Constants.RESERVED.ENV, args.env || process.env.NODE_ENV || Constants.RESERVED.ENV_DEV, true); }; /** * Configure custom logger. */ var configLogger = function(app) { if (process.env.POMELO_LOGGER !== 'off') { var env = app.get(Constants.RESERVED.ENV); var originPath = path.join(app.getBase(), Constants.FILEPATH.LOG); var presentPath = path.join(app.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.LOG)); if(fs.existsSync(originPath)) { log.configure(app, originPath); } else if(fs.existsSync(presentPath)) { log.configure(app, presentPath); } else { logger.error('logger file path configuration is error.'); } } }; /** * Parse command line arguments. * * @param args command line arguments * * @return Object argsMap map of arguments */ var parseArgs = function(args) { var argsMap = {}; var mainPos = 1; while (args[mainPos].indexOf('--') > 0) { mainPos++; } argsMap.main = args[mainPos]; for (var i = (mainPos + 1); i < args.length; i++) { var arg = args[i]; var sep = arg.indexOf('='); var key = arg.slice(0, sep); var value = arg.slice(sep + 1); if (!isNaN(Number(value)) && (value.indexOf('.') < 0)) { value = Number(value); } argsMap[key] = value; } return argsMap; }; /** * Load lifecycle file. * */ var loadLifecycle = function(app) { var filePath = path.join(app.getBase(), Constants.FILEPATH.SERVER_DIR, app.serverType, Constants.FILEPATH.LIFECYCLE); if(!fs.existsSync(filePath)) { return; } var lifecycle = require(filePath); for(var key in lifecycle) { if(typeof lifecycle[key] === 'function') { app.lifecycleCbs[key] = lifecycle[key]; } else { logger.warn('lifecycle.js in %s is error format.', filePath); } } }; ================================================ FILE: lib/util/constants.js ================================================ module.exports = { KEYWORDS: { BEFORE_FILTER: '__befores__', AFTER_FILTER: '__afters__', GLOBAL_BEFORE_FILTER: '__globalBefores__', GLOBAL_AFTER_FILTER: '__globalAfters__', ROUTE: '__routes__', BEFORE_STOP_HOOK: '__beforeStopHook__', MODULE: '__modules__', SERVER_MAP: '__serverMap__', RPC_BEFORE_FILTER: '__rpcBefores__', RPC_AFTER_FILTER: '__rpcAfters__', MASTER_WATCHER: '__masterwatcher__', MONITOR_WATCHER: '__monitorwatcher__' }, FILEPATH: { MASTER: '/config/master.json', SERVER: '/config/servers.json', CRON: '/config/crons.json', LOG: '/config/log4js.json', SERVER_PROTOS: '/config/serverProtos.json', CLIENT_PROTOS: '/config/clientProtos.json', MASTER_HA: '/config/masterha.json', LIFECYCLE: '/lifecycle.js', SERVER_DIR: '/app/servers/', CONFIG_DIR: '/config' }, DIR: { HANDLER: 'handler', REMOTE: 'remote', CRON: 'cron', LOG: 'logs', SCRIPT: 'scripts', EVENT: 'events', COMPONENT: 'components' }, RESERVED: { BASE: 'base', MAIN: 'main', MASTER: 'master', SERVERS: 'servers', ENV: 'env', CPU: 'cpu', ENV_DEV: 'development', ENV_PRO: 'production', ALL: 'all', SERVER_TYPE: 'serverType', SERVER_ID: 'serverId', CURRENT_SERVER: 'curServer', MODE: 'mode', TYPE: 'type', CLUSTER: 'clusters', STAND_ALONE: 'stand-alone', START: 'start', AFTER_START: 'afterStart', CRONS: 'crons', ERROR_HANDLER: 'errorHandler', GLOBAL_ERROR_HANDLER: 'globalErrorHandler', AUTO_RESTART: 'auto-restart', RESTART_FORCE: 'restart-force', CLUSTER_COUNT: 'clusterCount', CLUSTER_PREFIX: 'cluster-server-', CLUSTER_SIGNAL: '++', RPC_ERROR_HANDLER: 'rpcErrorHandler', SERVER: 'server', CLIENT: 'client', STARTID: 'startId', STOP_SERVERS: 'stop_servers', SSH_CONFIG_PARAMS: 'ssh_config_params' }, COMMAND: { TASKSET: 'taskset', KILL: 'kill', TASKKILL: 'taskkill', SSH: 'ssh' }, PLATFORM: { WIN: 'win32', LINUX: 'linux' }, LIFECYCLE: { BEFORE_STARTUP: 'beforeStartup', BEFORE_SHUTDOWN: 'beforeShutdown', AFTER_STARTUP: 'afterStartup', AFTER_STARTALL: 'afterStartAll' }, SIGNAL: { FAIL: 0, OK: 1 }, TIME: { TIME_WAIT_STOP: 3 * 1000, TIME_WAIT_KILL: 5 * 1000, TIME_WAIT_RESTART: 5 * 1000, TIME_WAIT_COUNTDOWN: 10 * 1000, TIME_WAIT_MASTER_KILL: 2 * 60 * 1000, TIME_WAIT_MONITOR_KILL: 2 * 1000, TIME_WAIT_PING: 30 * 1000, TIME_WAIT_MAX_PING: 5 * 60 * 1000, DEFAULT_UDP_HEARTBEAT_TIME: 20 * 1000, DEFAULT_UDP_HEARTBEAT_TIMEOUT: 100 * 1000, DEFAULT_MQTT_HEARTBEAT_TIMEOUT: 90 * 1000 } }; ================================================ FILE: lib/util/countDownLatch.js ================================================ var exp = module.exports; /** * Count down to zero or timeout and invoke cb finally. */ var CountDownLatch = function(count, opts, cb) { this.count = count; this.cb = cb; var self = this; if (opts.timeout) { this.timerId = setTimeout(function() { self.cb(true); }, opts.timeout); } }; /** * Call when a task finish to count down. * * @api public */ CountDownLatch.prototype.done = function() { if(this.count <= 0) { throw new Error('illegal state.'); } this.count--; if (this.count === 0) { if (this.timerId) { clearTimeout(this.timerId); } this.cb(); } }; /** * Create a count down latch * * @param {Integer} count * @param {Object} opts, opts.timeout indicates timeout, optional param * @param {Function} cb, cb(isTimeout) * * @api public */ exp.createCountDownLatch = function(count, opts, cb) { if(!count || count <= 0) { throw new Error('count should be positive.'); } if (!cb && typeof opts === 'function') { cb = opts; opts = {}; } if(typeof cb !== 'function') { throw new Error('cb should be a function.'); } return new CountDownLatch(count, opts, cb); }; ================================================ FILE: lib/util/events.js ================================================ module.exports = { ADD_SERVERS: 'add_servers', REMOVE_SERVERS: 'remove_servers', REPLACE_SERVERS: 'replace_servers', BIND_SESSION: 'bind_session', UNBIND_SESSION:'unbind_session', CLOSE_SESSION: 'close_session', ADD_CRONS: 'add_crons', REMOVE_CRONS: 'remove_crons', START_SERVER: 'start_server', START_ALL: 'start_all' }; ================================================ FILE: lib/util/log.js ================================================ var logger = require('pomelo-logger'); /** * Configure pomelo logger */ module.exports.configure = function(app, filename) { var serverId = app.getServerId(); var base = app.getBase(); logger.configure(filename, {serverId: serverId, base: base}); }; ================================================ FILE: lib/util/moduleUtil.js ================================================ var os = require('os'); var admin = require('pomelo-admin'); var utils = require('./utils'); var Constants = require('./constants'); var pathUtil = require('./pathUtil'); var starter = require('../master/starter'); var logger = require('pomelo-logger').getLogger('pomelo', __filename); var pro = module.exports; /** * Load admin modules */ pro.loadModules = function(self, consoleService) { // load app register modules var _modules = self.app.get(Constants.KEYWORDS.MODULE); if(!_modules) { return; } var modules = []; for(var m in _modules){ modules.push(_modules[m]); } var record, moduleId, module; for(var i=0, l=modules.length; i= modules.length) { utils.invokeCallback(cb, err); return; } var module = modules[index]; if(module && typeof module.start === 'function') { module.start(function(err) { startModule(err, modules, index + 1, cb); }); } else { startModule(err, modules, index + 1, cb); } }; ================================================ FILE: lib/util/pathUtil.js ================================================ var fs = require('fs'); var path = require('path'); var Constants = require('./constants'); var exp = module.exports; /** * Get system remote service path * * @param {String} role server role: frontend, backend * @return {String} path string if the path exist else null */ exp.getSysRemotePath = function(role) { var p = path.join(__dirname, '/../common/remote/', role); return fs.existsSync(p) ? p : null; }; /** * Get user remote service path * * @param {String} appBase application base path * @param {String} serverType server type * @return {String} path string if the path exist else null */ exp.getUserRemotePath = function(appBase, serverType) { var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.REMOTE); return fs.existsSync(p) ? p : null; }; /** * Get user remote cron path * * @param {String} appBase application base path * @param {String} serverType server type * @return {String} path string if the path exist else null */ exp.getCronPath = function(appBase, serverType) { var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.CRON); return fs.existsSync(p) ? p : null; }; /** * List all the subdirectory names of user remote directory * which hold the codes for all the server types. * * @param {String} appBase application base path * @return {Array} all the subdiretory name under servers/ */ exp.listUserRemoteDir = function(appBase) { var base = path.join(appBase, '/app/servers/'); var files = fs.readdirSync(base); return files.filter(function(fn) { if(fn.charAt(0) === '.') { return false; } return fs.statSync(path.join(base, fn)).isDirectory(); }); }; /** * Compose remote path record * * @param {String} namespace remote path namespace, such as: 'sys', 'user' * @param {String} serverType * @param {String} path remote service source path * @return {Object} remote path record */ exp.remotePathRecord = function(namespace, serverType, path) { return {namespace: namespace, serverType: serverType, path: path}; }; /** * Get handler path * * @param {String} appBase application base path * @param {String} serverType server type * @return {String} path string if the path exist else null */ exp.getHandlerPath = function(appBase, serverType) { var p = path.join(appBase, '/app/servers/', serverType, Constants.DIR.HANDLER); return fs.existsSync(p) ? p : null; }; /** * Get admin script root path. * * @param {String} appBase application base path * @return {String} script path string */ exp.getScriptPath = function(appBase) { return path.join(appBase, Constants.DIR.SCRIPT); }; /** * Get logs path. * * @param {String} appBase application base path * @return {String} logs path string */ exp.getLogPath = function(appBase) { return path.join(appBase, Constants.DIR.LOG); }; ================================================ FILE: lib/util/utils.js ================================================ var os = require('os'); var util = require('util'); var exec = require('child_process').exec; var logger = require('pomelo-logger').getLogger('pomelo', __filename); var Constants = require('./constants'); var pomelo = require('../pomelo'); var utils = module.exports; /** * Invoke callback with check */ utils.invokeCallback = function(cb) { if (typeof cb === 'function') { var len = arguments.length; if(len == 1) { return cb(); } if(len == 2) { return cb(arguments[1]); } if(len == 3) { return cb(arguments[1], arguments[2]); } if(len == 4) { return cb(arguments[1], arguments[2], arguments[3]); } var args = Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; cb.apply(null, args); // cb.apply(null, Array.prototype.slice.call(arguments, 1)); } }; /** * Get the count of elements of object */ utils.size = function(obj) { var count = 0; for (var i in obj) { if (obj.hasOwnProperty(i) && typeof obj[i] !== 'function') { count++; } } return count; }; /** * Check a string whether ends with another string */ utils.endsWith = function(str, suffix) { if (typeof str !== 'string' || typeof suffix !== 'string' || suffix.length > str.length) { return false; } return str.indexOf(suffix, str.length - suffix.length) !== -1; }; /** * Check a string whether starts with another string */ utils.startsWith = function(str, prefix) { if (typeof str !== 'string' || typeof prefix !== 'string' || prefix.length > str.length) { return false; } return str.indexOf(prefix) === 0; }; /** * Compare the two arrays and return the difference. */ utils.arrayDiff = function(array1, array2) { var o = {}; for(var i = 0, len = array2.length; i < len; i++) { o[array2[i]] = true; } var result = []; for(i = 0, len = array1.length; i < len; i++) { var v = array1[i]; if(o[v]) continue; result.push(v); } return result; }; /* * Date format */ utils.format = function(date, format) { format = format || 'MMddhhmm'; var o = { "M+": date.getMonth() + 1, //month "d+": date.getDate(), //day "h+": date.getHours(), //hour "m+": date.getMinutes(), //minute "s+": date.getSeconds(), //second "q+": Math.floor((date.getMonth() + 3) / 3), //quarter "S": date.getMilliseconds() //millisecond }; if (/(y+)/.test(format)) { format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); } for (var k in o) { if (new RegExp("(" + k + ")").test(format)) { format = format.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)); } } return format; }; /** * check if has Chinese characters. */ utils.hasChineseChar = function(str) { if (/.*[\u4e00-\u9fa5]+.*$/.test(str)) { return true; } else { return false; } }; /** * transform unicode to utf8 */ utils.unicodeToUtf8 = function(str) { var i, len, ch; var utf8Str = ""; len = str.length; for (i = 0; i < len; i++) { ch = str.charCodeAt(i); if ((ch >= 0x0) && (ch <= 0x7F)) { utf8Str += str.charAt(i); } else if ((ch >= 0x80) && (ch <= 0x7FF)) { utf8Str += String.fromCharCode(0xc0 | ((ch >> 6) & 0x1F)); utf8Str += String.fromCharCode(0x80 | (ch & 0x3F)); } else if ((ch >= 0x800) && (ch <= 0xFFFF)) { utf8Str += String.fromCharCode(0xe0 | ((ch >> 12) & 0xF)); utf8Str += String.fromCharCode(0x80 | ((ch >> 6) & 0x3F)); utf8Str += String.fromCharCode(0x80 | (ch & 0x3F)); } else if ((ch >= 0x10000) && (ch <= 0x1FFFFF)) { utf8Str += String.fromCharCode(0xF0 | ((ch >> 18) & 0x7)); utf8Str += String.fromCharCode(0x80 | ((ch >> 12) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 6) & 0x3F)); utf8Str += String.fromCharCode(0x80 | (ch & 0x3F)); } else if ((ch >= 0x200000) && (ch <= 0x3FFFFFF)) { utf8Str += String.fromCharCode(0xF8 | ((ch >> 24) & 0x3)); utf8Str += String.fromCharCode(0x80 | ((ch >> 18) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 12) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 6) & 0x3F)); utf8Str += String.fromCharCode(0x80 | (ch & 0x3F)); } else if ((ch >= 0x4000000) && (ch <= 0x7FFFFFFF)) { utf8Str += String.fromCharCode(0xFC | ((ch >> 30) & 0x1)); utf8Str += String.fromCharCode(0x80 | ((ch >> 24) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 18) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 12) & 0x3F)); utf8Str += String.fromCharCode(0x80 | ((ch >> 6) & 0x3F)); utf8Str += String.fromCharCode(0x80 | (ch & 0x3F)); } } return utf8Str; }; /** * Ping server to check if network is available * */ utils.ping = function(host, cb) { if(!module.exports.isLocal(host)) { var cmd = 'ping -w 15 ' + host; exec(cmd, function(err, stdout, stderr) { if(!!err) { cb(false); return; } cb(true); }); } else { cb(true); } }; /** * Check if server is exsit. * */ utils.checkPort = function(server, cb) { if (!server.port && !server.clientPort) { this.invokeCallback(cb, 'leisure'); return; } var self = this; var port = server.port || server.clientPort; var host = server.host; var generateCommand = function(self, host, port) { var cmd; var ssh_params = pomelo.app.get(Constants.RESERVED.SSH_CONFIG_PARAMS); if(!!ssh_params && Array.isArray(ssh_params)) { ssh_params = ssh_params.join(' '); } else { ssh_params = ""; } if (!self.isLocal(host)) { cmd = util.format('ssh %s %s "netstat -an|awk \'{print $4}\'|grep %s|wc -l"', host, ssh_params, port); } else { cmd = util.format('netstat -an|awk \'{print $4}\'|grep %s|wc -l', port); } return cmd; }; var cmd1 = generateCommand(self, host, port); var child = exec(cmd1, function(err, stdout, stderr) { if(err) { logger.error('command %s execute with error: %j', cmd1, err.stack); self.invokeCallback(cb, 'error'); } else if(stdout.trim() !== '0') { self.invokeCallback(cb, 'busy'); } else { port = server.clientPort; var cmd2 = generateCommand(self, host, port); exec(cmd2, function(err, stdout, stderr) { if(err) { logger.error('command %s execute with error: %j', cmd2, err.stack); self.invokeCallback(cb, 'error'); } else if (stdout.trim() !== '0') { self.invokeCallback(cb, 'busy'); } else { self.invokeCallback(cb, 'leisure'); } }); } }); }; utils.isLocal = function(host) { var app = require('../pomelo').app; if(!app) { return host === '127.0.0.1' || host === 'localhost' || host === '0.0.0.0' || inLocal(host); } else { return host === '127.0.0.1' || host === 'localhost' || host === '0.0.0.0' || inLocal(host) || host === app.master.host; } }; /** * Load cluster server. * */ utils.loadCluster = function(app, server, serverMap) { var increaseFields = {}; var host = server.host; var count = parseInt(server[Constants.RESERVED.CLUSTER_COUNT]); var seq = app.clusterSeq[server.serverType]; if(!seq) { seq = 0; app.clusterSeq[server.serverType] = count; } else { app.clusterSeq[server.serverType] = seq + count; } for(var key in server) { var value = server[key].toString(); if(value.indexOf(Constants.RESERVED.CLUSTER_SIGNAL) > 0) { var base = server[key].slice(0, -2); increaseFields[key] = base; } } var clone = function(src) { var rs = {}; for(var key in src) { rs[key] = src[key]; } return rs; }; for(var i=0, l=seq; i 1) { len <<= 8; } len += headBuffer.readUInt8(i); } return len; }; var inLocal = function(host) { for (var index in localIps) { if (host === localIps[index]) { return true; } } return false; }; var localIps = function() { var ifaces = os.networkInterfaces(); var ips = []; var func = function(details) { if (details.family === 'IPv4') { ips.push(details.address); } }; for (var dev in ifaces) { ifaces[dev].forEach(func); } return ips; }(); utils.isObject = function(arg) { return typeof arg === 'object' && arg !== null; }; ================================================ FILE: package.json ================================================ { "name": "pomelo", "version": "2.2.7", "homepage": "https://github.com/NetEase/pomelo", "repository": { "type": "git", "url": "https://github.com/NetEase/pomelo.git" }, "scripts": { "test": "grunt" }, "bugs": { "url": "https://github.com/NetEase/pomelo/issues" }, "licenses": [{ "type": "MIT", "url": "https://github.com/NetEase/pomelo#license" }], "keywords": [ "pomelo", "framework", "game", "web", "realtime", "server" ], "dependencies": { "socket.io": "1.7.2", "async": "0.2.5", "seq-queue": "0.0.5", "crc": "0.2.0", "cliff": "0.1.8", "mkdirp": "0.3.3", "pomelo-loader": "0.0.6", "pomelo-rpc": "1.0.7", "pomelo-protocol": "0.1.6", "pomelo-admin": "1.0.1", "pomelo-logger": "0.1.7", "pomelo-scheduler": "0.3.9", "ws": "1.1.1", "pomelo-protobuf": "0.4.0", "node-bignumber": "1.2.1", "commander": "2.0.0", "mqtt": "0.3.9" }, "bin": { "pomelo": "./bin/pomelo" }, "devDependencies": { "should": "3.3.1", "mocha": ">=0.0.1", "muk": ">=0.0.1", "grunt": "~0.4.2", "grunt-mocha-test": "0.8.x", "grunt-contrib-clean": "0.5.x", "grunt-contrib-jshint": "~0.8.0", "blanket": "~1.1.6" } } ================================================ FILE: template/game-server/app/servers/connector/handler/entryHandler.js ================================================ module.exports = function(app) { return new Handler(app); }; var Handler = function(app) { this.app = app; }; /** * New client entry. * * @param {Object} msg request message * @param {Object} session current session object * @param {Function} next next step callback * @return {Void} */ Handler.prototype.entry = function(msg, session, next) { next(null, {code: 200, msg: 'game server is ok.'}); }; /** * Publish route for mqtt connector. * * @param {Object} msg request message * @param {Object} session current session object * @param {Function} next next step callback * @return {Void} */ Handler.prototype.publish = function(msg, session, next) { var result = { topic: 'publish', payload: JSON.stringify({code: 200, msg: 'publish message is ok.'}) }; next(null, result); }; /** * Subscribe route for mqtt connector. * * @param {Object} msg request message * @param {Object} session current session object * @param {Function} next next step callback * @return {Void} */ Handler.prototype.subscribe = function(msg, session, next) { var result = { topic: 'subscribe', payload: JSON.stringify({code: 200, msg: 'subscribe message is ok.'}) }; next(null, result); }; ================================================ FILE: template/game-server/app.js ================================================ var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, heartbeat : 3, useDict : true, useProtobuf : true }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/app.js.mqtt ================================================ var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.mqttconnector, publishRoute: 'connector.entryHandler.publish', subscribeRoute: 'connector.entryHandler.subscribe' }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/app.js.sio ================================================ var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.sioconnector, // 'websocket', 'polling-xhr', 'polling-jsonp', 'polling' transports : ['websocket', 'polling'], heartbeats : true, closeTimeout : 60 * 1000, heartbeatTimeout : 60 * 1000, heartbeatInterval : 25 * 1000 }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/app.js.sio.wss ================================================ var fs = require('fs'); var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', function(){ app.set('connectorConfig', { connector : pomelo.connectors.sioconnector, key: fs.readFileSync('../shared/server.key'), cert: fs.readFileSync('../shared/server.crt') }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/app.js.udp ================================================ var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', function(){ app.set('connectorConfig', { connector : pomelo.connectors.udpconnector, heartbeat : 3 }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/app.js.wss ================================================ var fs = require('fs'); var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); app.set('name', '$'); // app configuration app.configure('production|development', 'connector', function(){ app.set('connectorConfig', { connector : pomelo.connectors.hybridconnector, heartbeat : 3, useDict : true, useProtobuf : true, ssl: { type: 'wss', key: fs.readFileSync('../shared/server.key'), cert: fs.readFileSync('../shared/server.crt') } }); }); // start app app.start(); process.on('uncaughtException', function (err) { console.error(' Caught exception: ' + err.stack); }); ================================================ FILE: template/game-server/config/adminServer.json ================================================ [{ "type": "connector", "token": "agarxhqb98rpajloaxn34ga8xrunpagkjwlaw3ruxnpaagl29w4rxn" } ] ================================================ FILE: template/game-server/config/adminUser.json ================================================ [ { "id": "user-1", "username": "admin", "password": "admin", "level": 1 }, { "id": "user-2", "username": "monitor", "password": "monitor", "level": 2 }, { "id": "user-3", "username": "test", "password": "test", "level": 2 } ] ================================================ FILE: template/game-server/config/clientProtos.json ================================================ {} ================================================ FILE: template/game-server/config/dictionary.json ================================================ {} ================================================ FILE: template/game-server/config/log4js.json ================================================ { "appenders": [ { "type": "console" }, { "type": "file", "filename": "${opts:base}/logs/con-log-${opts:serverId}.log", "pattern": "connector", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups": 5, "category": "con-log" }, { "type": "file", "filename": "${opts:base}/logs/rpc-log-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups": 5, "category": "rpc-log" }, { "type": "file", "filename": "${opts:base}/logs/forward-log-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups": 5, "category": "forward-log" }, { "type": "file", "filename": "${opts:base}/logs/rpc-debug-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups": 5, "category": "rpc-debug" }, { "type": "file", "filename": "${opts:base}/logs/crash.log", "maxLogSize": 1048576, "layout": { "type": "basic" }, "backups": 5, "category":"crash-log" }, { "type": "file", "filename": "${opts:base}/logs/admin.log", "maxLogSize": 1048576, "layout": { "type": "basic" } ,"backups": 5, "category":"admin-log" }, { "type": "file", "filename": "${opts:base}/logs/pomelo-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" } ,"backups": 5, "category":"pomelo" }, { "type": "file", "filename": "${opts:base}/logs/pomelo-admin.log", "maxLogSize": 1048576, "layout": { "type": "basic" } ,"backups": 5, "category":"pomelo-admin" }, { "type": "file", "filename": "${opts:base}/logs/pomelo-rpc-${opts:serverId}.log", "maxLogSize": 1048576, "layout": { "type": "basic" } ,"backups": 5, "category":"pomelo-rpc" } ], "levels": { "rpc-log" : "ERROR", "forward-log": "ERROR" }, "replaceConsole": true, "lineDebug": false } ================================================ FILE: template/game-server/config/master.json ================================================ { "development": { "id": "master-server-1", "host": "127.0.0.1", "port": 3005 }, "production": { "id": "master-server-1", "host": "127.0.0.1", "port": 3005 } } ================================================ FILE: template/game-server/config/serverProtos.json ================================================ {} ================================================ FILE: template/game-server/config/servers.json ================================================ { "development":{ "connector": [ {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true} ] }, "production":{ "connector": [ {"id": "connector-server-1", "host": "127.0.0.1", "port": 3150, "clientHost": "127.0.0.1", "clientPort": 3010, "frontend": true} ] } } ================================================ FILE: template/game-server/package.json ================================================ { "name":"$", "version":"0.0.1", "private":false, "dependencies":{ "pomelo":"#" } } ================================================ FILE: template/npm-install.bat ================================================ ::npm-install.bat @echo off ::install web server dependencies && game server dependencies cd web-server && npm install -d && cd .. && cd game-server && npm install -d ================================================ FILE: template/npm-install.sh ================================================ cd ./game-server && npm install -d echo '============ game-server npm installed ============' cd .. cd ./web-server && npm install -d echo '============ web-server npm installed ============' ================================================ FILE: template/shared/server.crt ================================================ -----BEGIN CERTIFICATE----- MIICSzCCAbQCCQCQVN8rD6MylDANBgkqhkiG9w0BAQUFADBqMQswCQYDVQQGEwJD TjERMA8GA1UECAwIemhlamlhbmcxETAPBgNVBAcMCGhhbmd6aG91MRAwDgYDVQQK DAdOZXRFYXNlMQ8wDQYDVQQLDAZwb21lbG8xEjAQBgNVBAMMCWxvY2FsaG9zdDAe Fw0xNDA0MjIwNjEwMDJaFw0xNDA1MjIwNjEwMDJaMGoxCzAJBgNVBAYTAkNOMREw DwYDVQQIDAh6aGVqaWFuZzERMA8GA1UEBwwIaGFuZ3pob3UxEDAOBgNVBAoMB05l dEVhc2UxDzANBgNVBAsMBnBvbWVsbzESMBAGA1UEAwwJbG9jYWxob3N0MIGfMA0G CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50 l5+ptOVPMK3wgEj+YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEF GRE5ul/X1M6I+F0UyTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4r Xobm9hoEFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAMIuL8KqEEtjbfL/tR2+5dQ5 958gtDtA62L7bMosl4hmuzdyWADu3IcKSaXAESLhIuIClt2Pwc14iFf9qRyB/cjY 4kLgwDGhK5EJw1kQS+Hs9NNSGxJTXUkoms3kEdRGy4hrZpTheJJNaKuv3oXrdvYQ 85yoc/P5OnJapB3huYL9 -----END CERTIFICATE----- ================================================ FILE: template/shared/server.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDMPe8oscKpTlQFZRrpbWmSO1UE+H65nq50l5+ptOVPMK3wgEj+ YRyGWhBjugj9teVmLXY9ImWdZkBlvdAiQj7/S/1MxRbRtwEFGRE5ul/X1M6I+F0U yTGYA1Mo0jIlQaBDXAAyDujCWi+qlyZ28efNDUlO2KBY1H4rXobm9hoEFQIDAQAB AoGAXhaeCUIyqeoynLWh+yzzOHFqzjpnrr0iIwYCgJycEqobRzLh7YXxLRdqe3al U7Oq9TI2SR2CcEs9mWEi89VOzVvfu+4zRlvJLMzNjG8ncdvzmzWR288ORq6qmYVU 3KAEz/tbNaQMLrD43hkIb9BrSIb/cnwekl3pANo9dwytU5UCQQD4V6vTyzs/ob21 +fO98tFkPtoHbt43S/1kDBSUyh6WWbS1KIQgtUSr2P5Ddtl6/vD3DW+XHCAhxyfV vuDvaP/fAkEA0oomFfmlpvzYejYNKPOz2PR+M0oRFVwn7lYyNwbRtUK1JYOMHwJ/ 3gwQEgAcYEkvgRlsxX0T5vHNmoR3U3OqiwJAIWkiG9devDvVWxMqoKZ3V0ZBbPiU etoFWB1r82yR2uZssmamCAR7HaeO5aKqtapw3rv3BFxrUkAJ8u7AMlVs/wJAVnpm MGqNjyyWIoSnHSYUvk2WtKx8neBvimcfUxja9HAFBfaljGszaFpeE3a2MRp+h7GQ ywGYNikmAYzdkoqVBwJAcOm/6u863pD2xA1mSFnmm3TulAMBfCULLdcY40w9m38b D89R1ISEy//N1fWa4KTsM0GpVOowEyluc53XNRUghw== -----END RSA PRIVATE KEY----- ================================================ FILE: template/web-server/app.js ================================================ var express = require('express'); var app = express.createServer(); app.configure(function(){ app.use(express.methodOverride()); app.use(express.bodyParser()); app.use(app.router); app.set('view engine', 'jade'); app.set('views', __dirname + '/public'); app.set('view options', {layout: false}); app.set('basepath',__dirname + '/public'); }); app.configure('development', function(){ app.use(express.static(__dirname + '/public')); app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); }); app.configure('production', function(){ var oneYear = 31557600000; app.use(express.static(__dirname + '/public', { maxAge: oneYear })); app.use(express.errorHandler()); }); console.log("Web server has started.\nPlease log on http://127.0.0.1:3001/index.html"); app.listen(3001); ================================================ FILE: template/web-server/app.js.https ================================================ var https = require('https'); var express = require('express'); var fs = require('fs'); var options = { key: fs.readFileSync('../shared/server.key'), cert: fs.readFileSync('../shared/server.crt') }; var app = express(); app.configure(function(){ app.use(express.methodOverride()); app.use(express.bodyParser()); app.use(app.router); app.set('view engine', 'jade'); app.set('views', __dirname + '/public'); app.set('view options', {layout: false}); app.set('basepath',__dirname + '/public'); }); app.configure('development', function(){ app.use(express.static(__dirname + '/public')); app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); }); app.configure('production', function(){ var oneYear = 31557600000; app.use(express.static(__dirname + '/public', { maxAge: oneYear })); app.use(express.errorHandler()); }); console.log("Web server has started.\nPlease log on http://127.0.0.1:3001/index.html"); var httpsServer = https.createServer(options, app); httpsServer.listen(3001); ================================================ FILE: template/web-server/bin/component.bat ================================================ cd public/js/lib && component install -f && component build -v ================================================ FILE: template/web-server/bin/component.sh ================================================ cd public/js/lib && component install -f && component build -v ================================================ FILE: template/web-server/package.json ================================================ { "name": "$", "version": "0.0.1", "private": false, "dependencies": { "express": "3.4.8" } } ================================================ FILE: template/web-server/public/css/base.css ================================================ .g-doc { width: 1000px; margin: 0 auto; text-align: left; line-height: 18px; font-size: 12px; color: #555; font-family: arial; } .g-banner { position: relative; width: 1000px; height: 90px; margin: 0 auto; } .g-banner .logo { position: absolute; left: 20px; top: 13px; width: 153px; height: 60px; z-index: 101; } .g-banner .logo .img { width: 153px; height: 60px; background: url(../image/logo.png) no-repeat 0 0; } .g-background { height: 350px; background: url(../image/sp.png) no-repeat 326px 0; } .g-content { padding: 150px; width: 800px; margin-left: 50px; font-size: 50pt; font-style: italic; } .g-link { height: 100px; margin-top: 30px; margin-left: 300px; font-size: 15pt; } .g-button { margin-top: 10px; text-align: center; } a:link { color: #006699; text-decoration: none; } a:visited { color: #006699; text-decoration: none; } a:hover { color: #FF0000; text-decoration: underline; } a:active { color: #006699; text-decoration: underline; } ================================================ FILE: template/web-server/public/index.html ================================================ Pomelo
Welcome to Pomelo
================================================ FILE: template/web-server/public/index.html.sio ================================================ Pomelo
Welcome to Pomelo
================================================ FILE: template/web-server/public/js/lib/build/build.js ================================================ /** * hasOwnProperty. */ var has = Object.prototype.hasOwnProperty; /* Refer to https://github.com/componentjs/require/blob/master/lib/require.js */ /** * Require the given path. * * @param {String} path * @return {Object} exports * @api public */ function require(path, parent, orig) { var resolved = require.resolve(path); // lookup failed if (null == resolved) { orig = orig || path; parent = parent || 'root'; var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); err.path = orig; err.parent = parent; err.require = true; throw err; } var module = require.modules[resolved]; // perform real require() // by invoking the module's // registered function if (!module.exports) { module.exports = {}; module.client = module.component = true; module.call(this, module.exports, require.relative(resolved), module); } return module.exports; } /** * Registered modules. */ require.modules = {}; /** * Registered aliases. */ require.aliases = {}; /** * Resolve `path`. * * Lookup: * * - PATH/index.js * - PATH.js * - PATH * * @param {String} path * @return {String} path or null * @api private */ require.resolve = function(path) { if (path.charAt(0) === '/') path = path.slice(1); var index = path + '/index.js'; var paths = [ path, path + '.js', path + '.json', path + '/index.js', path + '/index.json' ]; for (var i = 0; i < paths.length; i++) { var path = paths[i]; if (has.call(require.modules, path)) return path; } if (has.call(require.aliases, index)) { return require.aliases[index]; } }; /** * Normalize `path` relative to the current path. * * @param {String} curr * @param {String} path * @return {String} * @api private */ require.normalize = function(curr, path) { var segs = []; if ('.' != path.charAt(0)) return path; curr = curr.split('/'); path = path.split('/'); for (var i = 0; i < path.length; ++i) { if ('..' == path[i]) { curr.pop(); } else if ('.' != path[i] && '' != path[i]) { segs.push(path[i]); } } return curr.concat(segs).join('/'); }; /** * Register module at `path` with callback `definition`. * * @param {String} path * @param {Function} definition * @api private */ require.register = function(path, definition) { require.modules[path] = definition; }; /** * Alias a module definition. * * @param {String} from * @param {String} to * @api private */ require.alias = function(from, to) { if (!has.call(require.modules, from)) { throw new Error('Failed to alias "' + from + '", it does not exist'); } require.aliases[to] = from; }; /** * Return a require function relative to the `parent` path. * * @param {String} parent * @return {Function} * @api private */ require.relative = function(parent) { var p = require.normalize(parent, '..'); /** * lastIndexOf helper. */ function lastIndexOf(arr, obj) { var i = arr.length; while (i--) { if (arr[i] === obj) return i; } return -1; } /** * The relative require() itself. */ function localRequire(path) { var resolved = localRequire.resolve(path); return require(resolved, parent, path); } /** * Resolve relative to the parent. */ localRequire.resolve = function(path) { var c = path.charAt(0); if ('/' == c) return path.slice(1); if ('.' == c) return require.normalize(p, path); // resolve deps by returning // the dep in the nearest "deps" // directory var segs = parent.split('/'); var i = lastIndexOf(segs, 'deps') + 1; if (!i) i = 0; path = segs.slice(0, i + 1).join('/') + '/deps/' + path; return path; }; /** * Check if module is defined at `path`. */ localRequire.exists = function(path) { return has.call(require.modules, localRequire.resolve(path)); }; return localRequire; }; require.register("component-indexof/index.js", function(exports, require, module){ var indexOf = [].indexOf; module.exports = function(arr, obj){ if (indexOf) return arr.indexOf(obj); for (var i = 0; i < arr.length; ++i) { if (arr[i] === obj) return i; } return -1; }; }); require.register("component-emitter/index.js", function(exports, require, module){ /** * Module dependencies. */ var index = require('indexof'); /** * Expose `Emitter`. */ module.exports = Emitter; /** * Initialize a new `Emitter`. * * @api public */ function Emitter(obj) { if (obj) return mixin(obj); }; /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin(obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key]; } return obj; } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = function(event, fn){ this._callbacks = this._callbacks || {}; (this._callbacks[event] = this._callbacks[event] || []) .push(fn); return this; }; /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function(event, fn){ var self = this; this._callbacks = this._callbacks || {}; function on() { self.off(event, on); fn.apply(this, arguments); } fn._off = on; this.on(event, on); return this; }; /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = function(event, fn){ this._callbacks = this._callbacks || {}; // all if (0 == arguments.length) { this._callbacks = {}; return this; } // specific event var callbacks = this._callbacks[event]; if (!callbacks) return this; // remove all handlers if (1 == arguments.length) { delete this._callbacks[event]; return this; } // remove specific handler var i = index(callbacks, fn._off || fn); if (~i) callbacks.splice(i, 1); return this; }; /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function(event){ this._callbacks = this._callbacks || {}; var args = [].slice.call(arguments, 1) , callbacks = this._callbacks[event]; if (callbacks) { callbacks = callbacks.slice(0); for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args); } } return this; }; /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function(event){ this._callbacks = this._callbacks || {}; return this._callbacks[event] || []; }; /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function(event){ return !! this.listeners(event).length; }; }); require.register("NetEase-pomelo-protocol/lib/protocol.js", function(exports, require, module){ (function (exports, ByteArray, global) { var Protocol = exports; var PKG_HEAD_BYTES = 4; var MSG_FLAG_BYTES = 1; var MSG_ROUTE_CODE_BYTES = 2; var MSG_ID_MAX_BYTES = 5; var MSG_ROUTE_LEN_BYTES = 1; var MSG_ROUTE_CODE_MAX = 0xffff; var MSG_COMPRESS_ROUTE_MASK = 0x1; var MSG_TYPE_MASK = 0x7; var Package = Protocol.Package = {}; var Message = Protocol.Message = {}; Package.TYPE_HANDSHAKE = 1; Package.TYPE_HANDSHAKE_ACK = 2; Package.TYPE_HEARTBEAT = 3; Package.TYPE_DATA = 4; Package.TYPE_KICK = 5; Message.TYPE_REQUEST = 0; Message.TYPE_NOTIFY = 1; Message.TYPE_RESPONSE = 2; Message.TYPE_PUSH = 3; /** * pomele client encode * id message id; * route message route * msg message body * socketio current support string */ Protocol.strencode = function(str) { var byteArray = new ByteArray(str.length * 3); var offset = 0; for(var i = 0; i < str.length; i++){ var charCode = str.charCodeAt(i); var codes = null; if(charCode <= 0x7f){ codes = [charCode]; }else if(charCode <= 0x7ff){ codes = [0xc0|(charCode>>6), 0x80|(charCode & 0x3f)]; }else{ codes = [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; } for(var j = 0; j < codes.length; j++){ byteArray[offset] = codes[j]; ++offset; } } var _buffer = new ByteArray(offset); copyArray(_buffer, 0, byteArray, 0, offset); return _buffer; }; /** * client decode * msg String data * return Message Object */ Protocol.strdecode = function(buffer) { var bytes = new ByteArray(buffer); var array = []; var offset = 0; var charCode = 0; var end = bytes.length; while(offset < end){ if(bytes[offset] < 128){ charCode = bytes[offset]; offset += 1; }else if(bytes[offset] < 224){ charCode = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); offset += 2; }else{ charCode = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); offset += 3; } array.push(charCode); } var res = ''; var chunk = 8 * 1024; var i; for (i = 0; i < array.length / chunk; i++) { res += String.fromCharCode.apply(null, array.slice(i * chunk, (i + 1) * chunk)); } res += String.fromCharCode.apply(null, array.slice(i * chunk)); return res; }; /** * Package protocol encode. * * Pomelo package format: * +------+-------------+------------------+ * | type | body length | body | * +------+-------------+------------------+ * * Head: 4bytes * 0: package type, * 1 - handshake, * 2 - handshake ack, * 3 - heartbeat, * 4 - data * 5 - kick * 1 - 3: big-endian body length * Body: body length bytes * * @param {Number} type package type * @param {ByteArray} body body content in bytes * @return {ByteArray} new byte array that contains encode result */ Package.encode = function(type, body){ var length = body ? body.length : 0; var buffer = new ByteArray(PKG_HEAD_BYTES + length); var index = 0; buffer[index++] = type & 0xff; buffer[index++] = (length >> 16) & 0xff; buffer[index++] = (length >> 8) & 0xff; buffer[index++] = length & 0xff; if(body) { copyArray(buffer, index, body, 0, length); } return buffer; }; /** * Package protocol decode. * See encode for package format. * * @param {ByteArray} buffer byte array containing package content * @return {Object} {type: package type, buffer: body byte array} */ Package.decode = function(buffer){ var bytes = new ByteArray(buffer); var type = bytes[0]; var index = 1; var length = ((bytes[index++]) << 16 | (bytes[index++]) << 8 | bytes[index++]) >>> 0; var body = length ? new ByteArray(length) : null; copyArray(body, 0, bytes, PKG_HEAD_BYTES, length); return {'type': type, 'body': body}; }; /** * Message protocol encode. * * @param {Number} id message id * @param {Number} type message type * @param {Number} compressRoute whether compress route * @param {Number|String} route route code or route string * @param {Buffer} msg message body bytes * @return {Buffer} encode result */ Message.encode = function(id, type, compressRoute, route, msg){ // caculate message max length var idBytes = msgHasId(type) ? caculateMsgIdBytes(id) : 0; var msgLen = MSG_FLAG_BYTES + idBytes; if(msgHasRoute(type)) { if(compressRoute) { if(typeof route !== 'number'){ throw new Error('error flag for number route!'); } msgLen += MSG_ROUTE_CODE_BYTES; } else { msgLen += MSG_ROUTE_LEN_BYTES; if(route) { route = Protocol.strencode(route); if(route.length>255) { throw new Error('route maxlength is overflow'); } msgLen += route.length; } } } if(msg) { msgLen += msg.length; } var buffer = new ByteArray(msgLen); var offset = 0; // add flag offset = encodeMsgFlag(type, compressRoute, buffer, offset); // add message id if(msgHasId(type)) { offset = encodeMsgId(id, idBytes, buffer, offset); } // add route if(msgHasRoute(type)) { offset = encodeMsgRoute(compressRoute, route, buffer, offset); } // add body if(msg) { offset = encodeMsgBody(msg, buffer, offset); } return buffer; }; /** * Message protocol decode. * * @param {Buffer|Uint8Array} buffer message bytes * @return {Object} message object */ Message.decode = function(buffer) { var bytes = new ByteArray(buffer); var bytesLen = bytes.length || bytes.byteLength; var offset = 0; var id = 0; var route = null; // parse flag var flag = bytes[offset++]; var compressRoute = flag & MSG_COMPRESS_ROUTE_MASK; var type = (flag >> 1) & MSG_TYPE_MASK; // parse id if(msgHasId(type)) { var byte = bytes[offset++]; id = byte & 0x7f; while(byte & 0x80) { id <<= 7; byte = bytes[offset++]; id |= byte & 0x7f; } } // parse route if(msgHasRoute(type)) { if(compressRoute) { route = (bytes[offset++]) << 8 | bytes[offset++]; } else { var routeLen = bytes[offset++]; if(routeLen) { route = new ByteArray(routeLen); copyArray(route, 0, bytes, offset, routeLen); route = Protocol.strdecode(route); } else { route = ''; } offset += routeLen; } } // parse body var bodyLen = bytesLen - offset; var body = new ByteArray(bodyLen); copyArray(body, 0, bytes, offset, bodyLen); return {'id': id, 'type': type, 'compressRoute': compressRoute, 'route': route, 'body': body}; }; var copyArray = function(dest, doffset, src, soffset, length) { if('function' === typeof src.copy) { // Buffer src.copy(dest, doffset, soffset, soffset + length); } else { // Uint8Array for(var index=0; index>= 7; } while(id > 0); return len; }; var encodeMsgFlag = function(type, compressRoute, buffer, offset) { if(type !== Message.TYPE_REQUEST && type !== Message.TYPE_NOTIFY && type !== Message.TYPE_RESPONSE && type !== Message.TYPE_PUSH) { throw new Error('unkonw message type: ' + type); } buffer[offset] = (type << 1) | (compressRoute ? 1 : 0); return offset + MSG_FLAG_BYTES; }; var encodeMsgId = function(id, idBytes, buffer, offset) { var index = offset + idBytes - 1; buffer[index--] = id & 0x7f; while(index >= offset) { id >>= 7; buffer[index--] = id & 0x7f | 0x80; } return offset + idBytes; }; var encodeMsgRoute = function(compressRoute, route, buffer, offset) { if (compressRoute) { if(route > MSG_ROUTE_CODE_MAX){ throw new Error('route number is overflow'); } buffer[offset++] = (route >> 8) & 0xff; buffer[offset++] = route & 0xff; } else { if(route) { buffer[offset++] = route.length & 0xff; copyArray(buffer, offset, route, 0, route.length); offset += route.length; } else { buffer[offset++] = 0; } } return offset; }; var encodeMsgBody = function(msg, buffer, offset) { copyArray(buffer, offset, msg, 0, msg.length); return offset + msg.length; }; module.exports = Protocol; })('object' === typeof module ? module.exports : (this.Protocol = {}),'object' === typeof module ? Buffer : Uint8Array, this); }); require.register("pomelonode-pomelo-protobuf/lib/client/protobuf.js", function(exports, require, module){ /* ProtocolBuffer client 0.1.0*/ /** * pomelo-protobuf * @author */ /** * Protocol buffer root * In browser, it will be window.protbuf */ (function (exports, global){ var Protobuf = exports; Protobuf.init = function(opts){ //On the serverside, use serverProtos to encode messages send to client Protobuf.encoder.init(opts.encoderProtos); //On the serverside, user clientProtos to decode messages receive from clients Protobuf.decoder.init(opts.decoderProtos); }; Protobuf.encode = function(key, msg){ return Protobuf.encoder.encode(key, msg); }; Protobuf.decode = function(key, msg){ return Protobuf.decoder.decode(key, msg); }; // exports to support for components module.exports = Protobuf; })('object' === typeof module ? module.exports : (this.protobuf = {}), this); /** * constants */ (function (exports, global){ var constants = exports.constants = {}; constants.TYPES = { uInt32 : 0, sInt32 : 0, int32 : 0, double : 1, string : 2, message : 2, float : 5 }; })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * util module */ (function (exports, global){ var Util = exports.util = {}; Util.isSimpleType = function(type){ return ( type === 'uInt32' || type === 'sInt32' || type === 'int32' || type === 'uInt64' || type === 'sInt64' || type === 'float' || type === 'double' ); }; })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * codec module */ (function (exports, global){ var Codec = exports.codec = {}; var buffer = new ArrayBuffer(8); var float32Array = new Float32Array(buffer); var float64Array = new Float64Array(buffer); var uInt8Array = new Uint8Array(buffer); Codec.encodeUInt32 = function(n){ var n = parseInt(n); if(isNaN(n) || n < 0){ return null; } var result = []; do{ var tmp = n % 128; var next = Math.floor(n/128); if(next !== 0){ tmp = tmp + 128; } result.push(tmp); n = next; }while(n !== 0); return result; }; Codec.encodeSInt32 = function(n){ var n = parseInt(n); if(isNaN(n)){ return null; } n = n<0?(Math.abs(n)*2-1):n*2; return Codec.encodeUInt32(n); }; Codec.decodeUInt32 = function(bytes){ var n = 0; for(var i = 0; i < bytes.length; i++){ var m = parseInt(bytes[i]); n = n + ((m & 0x7f) * Math.pow(2,(7*i))); if(m < 128){ return n; } } return n; }; Codec.decodeSInt32 = function(bytes){ var n = this.decodeUInt32(bytes); var flag = ((n%2) === 1)?-1:1; n = ((n%2 + n)/2)*flag; return n; }; Codec.encodeFloat = function(float){ float32Array[0] = float; return uInt8Array; }; Codec.decodeFloat = function(bytes, offset){ if(!bytes || bytes.length < (offset +4)){ return null; } for(var i = 0; i < 4; i++){ uInt8Array[i] = bytes[offset + i]; } return float32Array[0]; }; Codec.encodeDouble = function(double){ float64Array[0] = double; return uInt8Array.subarray(0, 8); }; Codec.decodeDouble = function(bytes, offset){ if(!bytes || bytes.length < (8 + offset)){ return null; } for(var i = 0; i < 8; i++){ uInt8Array[i] = bytes[offset + i]; } return float64Array[0]; }; Codec.encodeStr = function(bytes, offset, str){ for(var i = 0; i < str.length; i++){ var code = str.charCodeAt(i); var codes = encode2UTF8(code); for(var j = 0; j < codes.length; j++){ bytes[offset] = codes[j]; offset++; } } return offset; }; /** * Decode string from utf8 bytes */ Codec.decodeStr = function(bytes, offset, length){ var array = []; var end = offset + length; while(offset < end){ var code = 0; if(bytes[offset] < 128){ code = bytes[offset]; offset += 1; }else if(bytes[offset] < 224){ code = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); offset += 2; }else{ code = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); offset += 3; } array.push(code); } var str = ''; for(var i = 0; i < array.length;){ str += String.fromCharCode.apply(null, array.slice(i, i + 10000)); i += 10000; } return str; }; /** * Return the byte length of the str use utf8 */ Codec.byteLength = function(str){ if(typeof(str) !== 'string'){ return -1; } var length = 0; for(var i = 0; i < str.length; i++){ var code = str.charCodeAt(i); length += codeLength(code); } return length; }; /** * Encode a unicode16 char code to utf8 bytes */ function encode2UTF8(charCode){ if(charCode <= 0x7f){ return [charCode]; }else if(charCode <= 0x7ff){ return [0xc0|(charCode>>6), 0x80|(charCode & 0x3f)]; }else{ return [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; } } function codeLength(code){ if(code <= 0x7f){ return 1; }else if(code <= 0x7ff){ return 2; }else{ return 3; } } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * encoder module */ (function (exports, global){ var protobuf = exports; var MsgEncoder = exports.encoder = {}; var codec = protobuf.codec; var constant = protobuf.constants; var util = protobuf.util; MsgEncoder.init = function(protos){ this.protos = protos || {}; }; MsgEncoder.encode = function(route, msg){ //Get protos from protos map use the route as key var protos = this.protos[route]; //Check msg if(!checkMsg(msg, protos)){ return null; } //Set the length of the buffer 2 times bigger to prevent overflow var length = codec.byteLength(JSON.stringify(msg)); //Init buffer and offset var buffer = new ArrayBuffer(length); var uInt8Array = new Uint8Array(buffer); var offset = 0; if(!!protos){ offset = encodeMsg(uInt8Array, offset, protos, msg); if(offset > 0){ return uInt8Array.subarray(0, offset); } } return null; }; /** * Check if the msg follow the defination in the protos */ function checkMsg(msg, protos){ if(!protos){ return false; } for(var name in protos){ var proto = protos[name]; //All required element must exist switch(proto.option){ case 'required' : if(typeof(msg[name]) === 'undefined'){ return false; } case 'optional' : if(typeof(msg[name]) !== 'undefined'){ if(!!protos.__messages[proto.type]){ checkMsg(msg[name], protos.__messages[proto.type]); } } break; case 'repeated' : //Check nest message in repeated elements if(!!msg[name] && !!protos.__messages[proto.type]){ for(var i = 0; i < msg[name].length; i++){ if(!checkMsg(msg[name][i], protos.__messages[proto.type])){ return false; } } } break; } } return true; } function encodeMsg(buffer, offset, protos, msg){ for(var name in msg){ if(!!protos[name]){ var proto = protos[name]; switch(proto.option){ case 'required' : case 'optional' : offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = encodeProp(msg[name], proto.type, offset, buffer, protos); break; case 'repeated' : if(msg[name].length > 0){ offset = encodeArray(msg[name], proto, offset, buffer, protos); } break; } } } return offset; } function encodeProp(value, type, offset, buffer, protos){ switch(type){ case 'uInt32': offset = writeBytes(buffer, offset, codec.encodeUInt32(value)); break; case 'int32' : case 'sInt32': offset = writeBytes(buffer, offset, codec.encodeSInt32(value)); break; case 'float': writeBytes(buffer, offset, codec.encodeFloat(value)); offset += 4; break; case 'double': writeBytes(buffer, offset, codec.encodeDouble(value)); offset += 8; break; case 'string': var length = codec.byteLength(value); //Encode length offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); //write string codec.encodeStr(buffer, offset, value); offset += length; break; default : if(!!protos.__messages[type]){ //Use a tmp buffer to build an internal msg var tmpBuffer = new ArrayBuffer(codec.byteLength(JSON.stringify(value))); var length = 0; length = encodeMsg(tmpBuffer, length, protos.__messages[type], value); //Encode length offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); //contact the object for(var i = 0; i < length; i++){ buffer[offset] = tmpBuffer[i]; offset++; } } break; } return offset; } /** * Encode reapeated properties, simple msg and object are decode differented */ function encodeArray(array, proto, offset, buffer, protos){ var i = 0; if(util.isSimpleType(proto.type)){ offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length)); for(i = 0; i < array.length; i++){ offset = encodeProp(array[i], proto.type, offset, buffer); } }else{ for(i = 0; i < array.length; i++){ offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = encodeProp(array[i], proto.type, offset, buffer, protos); } } return offset; } function writeBytes(buffer, offset, bytes){ for(var i = 0; i < bytes.length; i++, offset++){ buffer[offset] = bytes[i]; } return offset; } function encodeTag(type, tag){ var value = constant.TYPES[type]||2; return codec.encodeUInt32((tag<<3)|value); } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * decoder module */ (function (exports, global){ var protobuf = exports; var MsgDecoder = exports.decoder = {}; var codec = protobuf.codec; var util = protobuf.util; var buffer; var offset = 0; MsgDecoder.init = function(protos){ this.protos = protos || {}; }; MsgDecoder.setProtos = function(protos){ if(!!protos){ this.protos = protos; } }; MsgDecoder.decode = function(route, buf){ var protos = this.protos[route]; buffer = buf; offset = 0; if(!!protos){ return decodeMsg({}, protos, buffer.length); } return null; }; function decodeMsg(msg, protos, length){ while(offset>3 }; } /** * Get tag head without move the offset */ function peekHead(){ var tag = codec.decodeUInt32(peekBytes()); return { type : tag&0x7, tag : tag>>3 }; } function decodeProp(type, protos){ switch(type){ case 'uInt32': return codec.decodeUInt32(getBytes()); case 'int32' : case 'sInt32' : return codec.decodeSInt32(getBytes()); case 'float' : var float = codec.decodeFloat(buffer, offset); offset += 4; return float; case 'double' : var double = codec.decodeDouble(buffer, offset); offset += 8; return double; case 'string' : var length = codec.decodeUInt32(getBytes()); var str = codec.decodeStr(buffer, offset, length); offset += length; return str; default : if(!!protos && !!protos.__messages[type]){ var length = codec.decodeUInt32(getBytes()); var msg = {}; decodeMsg(msg, protos.__messages[type], offset+length); return msg; } break; } } function decodeArray(array, type, protos){ if(util.isSimpleType(type)){ var length = codec.decodeUInt32(getBytes()); for(var i = 0; i < length; i++){ array.push(decodeProp(type)); } }else{ array.push(decodeProp(type, protos)); } } function getBytes(flag){ var bytes = []; var pos = offset; flag = flag || false; var b; do{ b = buffer[pos]; bytes.push(b); pos++; }while(b >= 128); if(!flag){ offset = pos; } return bytes; } function peekBytes(){ return getBytes(true); } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); }); require.register("pomelonode-pomelo-jsclient-websocket/lib/pomelo-client.js", function(exports, require, module){ (function() { var JS_WS_CLIENT_TYPE = 'js-websocket'; var JS_WS_CLIENT_VERSION = '0.0.1'; var Protocol = window.Protocol; var Package = Protocol.Package; var Message = Protocol.Message; var EventEmitter = window.EventEmitter; var RES_OK = 200; var RES_FAIL = 500; var RES_OLD_CLIENT = 501; if (typeof Object.create !== 'function') { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; } var root = window; var pomelo = Object.create(EventEmitter.prototype); // object extend from object root.pomelo = pomelo; var socket = null; var reqId = 0; var callbacks = {}; var handlers = {}; //Map from request id to route var routeMap = {}; var heartbeatInterval = 0; var heartbeatTimeout = 0; var nextHeartbeatTimeout = 0; var gapThreshold = 100; // heartbeat gap threashold var heartbeatId = null; var heartbeatTimeoutId = null; var handshakeCallback = null; var handshakeBuffer = { 'sys': { type: JS_WS_CLIENT_TYPE, version: JS_WS_CLIENT_VERSION }, 'user': { } }; var initCallback = null; pomelo.init = function(params, cb){ initCallback = cb; var host = params.host; var port = params.port; var url = 'ws://' + host; if(port) { url += ':' + port; } handshakeBuffer.user = params.user; handshakeCallback = params.handshakeCallback; initWebSocket(url, cb); }; var initWebSocket = function(url,cb) { console.log('connect to ' + url); var onopen = function(event){ var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer))); send(obj); }; var onmessage = function(event) { processPackage(Package.decode(event.data), cb); // new package arrived, update the heartbeat timeout if(heartbeatTimeout) { nextHeartbeatTimeout = Date.now() + heartbeatTimeout; } }; var onerror = function(event) { pomelo.emit('io-error', event); console.error('socket error: ', event); }; var onclose = function(event){ pomelo.emit('close',event); console.error('socket close: ', event); }; socket = new WebSocket(url); socket.binaryType = 'arraybuffer'; socket.onopen = onopen; socket.onmessage = onmessage; socket.onerror = onerror; socket.onclose = onclose; }; pomelo.disconnect = function() { if(socket) { if(socket.disconnect) socket.disconnect(); if(socket.close) socket.close(); console.log('disconnect'); socket = null; } if(heartbeatId) { clearTimeout(heartbeatId); heartbeatId = null; } if(heartbeatTimeoutId) { clearTimeout(heartbeatTimeoutId); heartbeatTimeoutId = null; } }; pomelo.request = function(route, msg, cb) { if(arguments.length === 2 && typeof msg === 'function') { cb = msg; msg = {}; } else { msg = msg || {}; } route = route || msg.route; if(!route) { return; } reqId++; sendMessage(reqId, route, msg); callbacks[reqId] = cb; routeMap[reqId] = route; }; pomelo.notify = function(route, msg) { msg = msg || {}; sendMessage(0, route, msg); }; var sendMessage = function(reqId, route, msg) { var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; //compress message by protobuf var protos = !!pomelo.data.protos?pomelo.data.protos.client:{}; if(!!protos[route]){ msg = protobuf.encode(route, msg); }else{ msg = Protocol.strencode(JSON.stringify(msg)); } var compressRoute = 0; if(pomelo.dict && pomelo.dict[route]){ route = pomelo.dict[route]; compressRoute = 1; } msg = Message.encode(reqId, type, compressRoute, route, msg); var packet = Package.encode(Package.TYPE_DATA, msg); send(packet); }; var send = function(packet){ socket.send(packet.buffer); }; var handler = {}; var heartbeat = function(data) { if(!heartbeatInterval) { // no heartbeat return; } var obj = Package.encode(Package.TYPE_HEARTBEAT); if(heartbeatTimeoutId) { clearTimeout(heartbeatTimeoutId); heartbeatTimeoutId = null; } if(heartbeatId) { // already in a heartbeat interval return; } heartbeatId = setTimeout(function() { heartbeatId = null; send(obj); nextHeartbeatTimeout = Date.now() + heartbeatTimeout; heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout); }, heartbeatInterval); }; var heartbeatTimeoutCb = function() { var gap = nextHeartbeatTimeout - Date.now(); if(gap > gapThreshold) { heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap); } else { console.error('server heartbeat timeout'); pomelo.emit('heartbeat timeout'); pomelo.disconnect(); } }; var handshake = function(data){ data = JSON.parse(Protocol.strdecode(data)); if(data.code === RES_OLD_CLIENT) { pomelo.emit('error', 'client version not fullfill'); return; } if(data.code !== RES_OK) { pomelo.emit('error', 'handshake fail'); return; } handshakeInit(data); var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK); send(obj); if(initCallback) { initCallback(socket); initCallback = null; } }; var onData = function(data){ //probuff decode var msg = Message.decode(data); if(msg.id > 0){ msg.route = routeMap[msg.id]; delete routeMap[msg.id]; if(!msg.route){ return; } } msg.body = deCompose(msg); processMessage(pomelo, msg); }; var onKick = function(data) { pomelo.emit('onKick'); }; handlers[Package.TYPE_HANDSHAKE] = handshake; handlers[Package.TYPE_HEARTBEAT] = heartbeat; handlers[Package.TYPE_DATA] = onData; handlers[Package.TYPE_KICK] = onKick; var processPackage = function(msg) { handlers[msg.type](msg.body); }; var processMessage = function(pomelo, msg) { if(!msg.id) { // server push message pomelo.emit(msg.route, msg.body); return; } //if have a id then find the callback function with the request var cb = callbacks[msg.id]; delete callbacks[msg.id]; if(typeof cb !== 'function') { return; } cb(msg.body); return; }; var processMessageBatch = function(pomelo, msgs) { for(var i=0, l=msgs.length; i>6), 0x80|(charCode & 0x3f)]; }else{ codes = [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; } for(var j = 0; j < codes.length; j++){ byteArray[offset] = codes[j]; ++offset; } } var _buffer = new ByteArray(offset); copyArray(_buffer, 0, byteArray, 0, offset); return _buffer; }; /** * client decode * msg String data * return Message Object */ Protocol.strdecode = function(buffer) { var bytes = new ByteArray(buffer); var array = []; var offset = 0; var charCode = 0; var end = bytes.length; while(offset < end){ if(bytes[offset] < 128){ charCode = bytes[offset]; offset += 1; }else if(bytes[offset] < 224){ charCode = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); offset += 2; }else{ charCode = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); offset += 3; } array.push(charCode); } return String.fromCharCode.apply(null, array); }; /** * Package protocol encode. * * Pomelo package format: * +------+-------------+------------------+ * | type | body length | body | * +------+-------------+------------------+ * * Head: 4bytes * 0: package type, * 1 - handshake, * 2 - handshake ack, * 3 - heartbeat, * 4 - data * 5 - kick * 1 - 3: big-endian body length * Body: body length bytes * * @param {Number} type package type * @param {ByteArray} body body content in bytes * @return {ByteArray} new byte array that contains encode result */ Package.encode = function(type, body){ var length = body ? body.length : 0; var buffer = new ByteArray(PKG_HEAD_BYTES + length); var index = 0; buffer[index++] = type & 0xff; buffer[index++] = (length >> 16) & 0xff; buffer[index++] = (length >> 8) & 0xff; buffer[index++] = length & 0xff; if(body) { copyArray(buffer, index, body, 0, length); } return buffer; }; /** * Package protocol decode. * See encode for package format. * * @param {ByteArray} buffer byte array containing package content * @return {Object} {type: package type, buffer: body byte array} */ Package.decode = function(buffer){ var bytes = new ByteArray(buffer); var type = bytes[0]; var index = 1; var length = ((bytes[index++]) << 16 | (bytes[index++]) << 8 | bytes[index++]) >>> 0; var body = length ? new ByteArray(length) : null; copyArray(body, 0, bytes, PKG_HEAD_BYTES, length); return {'type': type, 'body': body}; }; /** * Message protocol encode. * * @param {Number} id message id * @param {Number} type message type * @param {Number} compressRoute whether compress route * @param {Number|String} route route code or route string * @param {Buffer} msg message body bytes * @return {Buffer} encode result */ Message.encode = function(id, type, compressRoute, route, msg){ // caculate message max length var idBytes = msgHasId(type) ? caculateMsgIdBytes(id) : 0; var msgLen = MSG_FLAG_BYTES + idBytes; if(msgHasRoute(type)) { if(compressRoute) { if(typeof route !== 'number'){ throw new Error('error flag for number route!'); } msgLen += MSG_ROUTE_CODE_BYTES; } else { msgLen += MSG_ROUTE_LEN_BYTES; if(route) { route = Protocol.strencode(route); if(route.length>255) { throw new Error('route maxlength is overflow'); } msgLen += route.length; } } } if(msg) { msgLen += msg.length; } var buffer = new ByteArray(msgLen); var offset = 0; // add flag offset = encodeMsgFlag(type, compressRoute, buffer, offset); // add message id if(msgHasId(type)) { offset = encodeMsgId(id, idBytes, buffer, offset); } // add route if(msgHasRoute(type)) { offset = encodeMsgRoute(compressRoute, route, buffer, offset); } // add body if(msg) { offset = encodeMsgBody(msg, buffer, offset); } return buffer; }; /** * Message protocol decode. * * @param {Buffer|Uint8Array} buffer message bytes * @return {Object} message object */ Message.decode = function(buffer) { var bytes = new ByteArray(buffer); var bytesLen = bytes.length || bytes.byteLength; var offset = 0; var id = 0; var route = null; // parse flag var flag = bytes[offset++]; var compressRoute = flag & MSG_COMPRESS_ROUTE_MASK; var type = (flag >> 1) & MSG_TYPE_MASK; // parse id if(msgHasId(type)) { var byte = bytes[offset++]; id = byte & 0x7f; while(byte & 0x80) { id <<= 7; byte = bytes[offset++]; id |= byte & 0x7f; } } // parse route if(msgHasRoute(type)) { if(compressRoute) { route = (bytes[offset++]) << 8 | bytes[offset++]; } else { var routeLen = bytes[offset++]; if(routeLen) { route = new ByteArray(routeLen); copyArray(route, 0, bytes, offset, routeLen); route = Protocol.strdecode(route); } else { route = ''; } offset += routeLen; } } // parse body var bodyLen = bytesLen - offset; var body = new ByteArray(bodyLen); copyArray(body, 0, bytes, offset, bodyLen); return {'id': id, 'type': type, 'compressRoute': compressRoute, 'route': route, 'body': body}; }; var copyArray = function(dest, doffset, src, soffset, length) { if('function' === typeof src.copy) { // Buffer src.copy(dest, doffset, soffset, soffset + length); } else { // Uint8Array for(var index=0; index>= 7; } while(id > 0); return len; }; var encodeMsgFlag = function(type, compressRoute, buffer, offset) { if(type !== Message.TYPE_REQUEST && type !== Message.TYPE_NOTIFY && type !== Message.TYPE_RESPONSE && type !== Message.TYPE_PUSH) { throw new Error('unkonw message type: ' + type); } buffer[offset] = (type << 1) | (compressRoute ? 1 : 0); return offset + MSG_FLAG_BYTES; }; var encodeMsgId = function(id, idBytes, buffer, offset) { var index = offset + idBytes - 1; buffer[index--] = id & 0x7f; while(index >= offset) { id >>= 7; buffer[index--] = id & 0x7f | 0x80; } return offset + idBytes; }; var encodeMsgRoute = function(compressRoute, route, buffer, offset) { if (compressRoute) { if(route > MSG_ROUTE_CODE_MAX){ throw new Error('route number is overflow'); } buffer[offset++] = (route >> 8) & 0xff; buffer[offset++] = route & 0xff; } else { if(route) { buffer[offset++] = route.length & 0xff; copyArray(buffer, offset, route, 0, route.length); offset += route.length; } else { buffer[offset++] = 0; } } return offset; }; var encodeMsgBody = function(msg, buffer, offset) { copyArray(buffer, offset, msg, 0, msg.length); return offset + msg.length; }; module.exports = Protocol; })('object' === typeof module ? module.exports : (this.Protocol = {}),'object' === typeof module ? Buffer : Uint8Array, this); }); require.register("pomelonode-pomelo-protobuf/lib/client/protobuf.js", function(exports, require, module){ /* ProtocolBuffer client 0.1.0*/ /** * pomelo-protobuf * @author */ /** * Protocol buffer root * In browser, it will be window.protbuf */ (function (exports, global){ var Protobuf = exports; Protobuf.init = function(opts){ //On the serverside, use serverProtos to encode messages send to client Protobuf.encoder.init(opts.encoderProtos); //On the serverside, user clientProtos to decode messages receive from clients Protobuf.decoder.init(opts.decoderProtos); }; Protobuf.encode = function(key, msg){ return Protobuf.encoder.encode(key, msg); }; Protobuf.decode = function(key, msg){ return Protobuf.decoder.decode(key, msg); }; // exports to support for components module.exports = Protobuf; })('object' === typeof module ? module.exports : (this.protobuf = {}), this); /** * constants */ (function (exports, global){ var constants = exports.constants = {}; constants.TYPES = { uInt32 : 0, sInt32 : 0, int32 : 0, double : 1, string : 2, message : 2, float : 5 }; })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * util module */ (function (exports, global){ var Util = exports.util = {}; Util.isSimpleType = function(type){ return ( type === 'uInt32' || type === 'sInt32' || type === 'int32' || type === 'uInt64' || type === 'sInt64' || type === 'float' || type === 'double' ); }; })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * codec module */ (function (exports, global){ var Codec = exports.codec = {}; var buffer = new ArrayBuffer(8); var float32Array = new Float32Array(buffer); var float64Array = new Float64Array(buffer); var uInt8Array = new Uint8Array(buffer); Codec.encodeUInt32 = function(n){ var n = parseInt(n); if(isNaN(n) || n < 0){ return null; } var result = []; do{ var tmp = n % 128; var next = Math.floor(n/128); if(next !== 0){ tmp = tmp + 128; } result.push(tmp); n = next; }while(n !== 0); return result; }; Codec.encodeSInt32 = function(n){ var n = parseInt(n); if(isNaN(n)){ return null; } n = n<0?(Math.abs(n)*2-1):n*2; return Codec.encodeUInt32(n); }; Codec.decodeUInt32 = function(bytes){ var n = 0; for(var i = 0; i < bytes.length; i++){ var m = parseInt(bytes[i]); n = n + ((m & 0x7f) * Math.pow(2,(7*i))); if(m < 128){ return n; } } return n; }; Codec.decodeSInt32 = function(bytes){ var n = this.decodeUInt32(bytes); var flag = ((n%2) === 1)?-1:1; n = ((n%2 + n)/2)*flag; return n; }; Codec.encodeFloat = function(float){ float32Array[0] = float; return uInt8Array; }; Codec.decodeFloat = function(bytes, offset){ if(!bytes || bytes.length < (offset +4)){ return null; } for(var i = 0; i < 4; i++){ uInt8Array[i] = bytes[offset + i]; } return float32Array[0]; }; Codec.encodeDouble = function(double){ float64Array[0] = double; return uInt8Array.subarray(0, 8); }; Codec.decodeDouble = function(bytes, offset){ if(!bytes || bytes.length < (8 + offset)){ return null; } for(var i = 0; i < 8; i++){ uInt8Array[i] = bytes[offset + i]; } return float64Array[0]; }; Codec.encodeStr = function(bytes, offset, str){ for(var i = 0; i < str.length; i++){ var code = str.charCodeAt(i); var codes = encode2UTF8(code); for(var j = 0; j < codes.length; j++){ bytes[offset] = codes[j]; offset++; } } return offset; }; /** * Decode string from utf8 bytes */ Codec.decodeStr = function(bytes, offset, length){ var array = []; var end = offset + length; while(offset < end){ var code = 0; if(bytes[offset] < 128){ code = bytes[offset]; offset += 1; }else if(bytes[offset] < 224){ code = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); offset += 2; }else{ code = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); offset += 3; } array.push(code); } var str = ''; for(var i = 0; i < array.length;){ str += String.fromCharCode.apply(null, array.slice(i, i + 10000)); i += 10000; } return str; }; /** * Return the byte length of the str use utf8 */ Codec.byteLength = function(str){ if(typeof(str) !== 'string'){ return -1; } var length = 0; for(var i = 0; i < str.length; i++){ var code = str.charCodeAt(i); length += codeLength(code); } return length; }; /** * Encode a unicode16 char code to utf8 bytes */ function encode2UTF8(charCode){ if(charCode <= 0x7f){ return [charCode]; }else if(charCode <= 0x7ff){ return [0xc0|(charCode>>6), 0x80|(charCode & 0x3f)]; }else{ return [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; } } function codeLength(code){ if(code <= 0x7f){ return 1; }else if(code <= 0x7ff){ return 2; }else{ return 3; } } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * encoder module */ (function (exports, global){ var protobuf = exports; var MsgEncoder = exports.encoder = {}; var codec = protobuf.codec; var constant = protobuf.constants; var util = protobuf.util; MsgEncoder.init = function(protos){ this.protos = protos || {}; }; MsgEncoder.encode = function(route, msg){ //Get protos from protos map use the route as key var protos = this.protos[route]; //Check msg if(!checkMsg(msg, protos)){ return null; } //Set the length of the buffer 2 times bigger to prevent overflow var length = codec.byteLength(JSON.stringify(msg)); //Init buffer and offset var buffer = new ArrayBuffer(length); var uInt8Array = new Uint8Array(buffer); var offset = 0; if(!!protos){ offset = encodeMsg(uInt8Array, offset, protos, msg); if(offset > 0){ return uInt8Array.subarray(0, offset); } } return null; }; /** * Check if the msg follow the defination in the protos */ function checkMsg(msg, protos){ if(!protos){ return false; } for(var name in protos){ var proto = protos[name]; //All required element must exist switch(proto.option){ case 'required' : if(typeof(msg[name]) === 'undefined'){ return false; } case 'optional' : if(typeof(msg[name]) !== 'undefined'){ if(!!protos.__messages[proto.type]){ checkMsg(msg[name], protos.__messages[proto.type]); } } break; case 'repeated' : //Check nest message in repeated elements if(!!msg[name] && !!protos.__messages[proto.type]){ for(var i = 0; i < msg[name].length; i++){ if(!checkMsg(msg[name][i], protos.__messages[proto.type])){ return false; } } } break; } } return true; } function encodeMsg(buffer, offset, protos, msg){ for(var name in msg){ if(!!protos[name]){ var proto = protos[name]; switch(proto.option){ case 'required' : case 'optional' : offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = encodeProp(msg[name], proto.type, offset, buffer, protos); break; case 'repeated' : if(msg[name].length > 0){ offset = encodeArray(msg[name], proto, offset, buffer, protos); } break; } } } return offset; } function encodeProp(value, type, offset, buffer, protos){ switch(type){ case 'uInt32': offset = writeBytes(buffer, offset, codec.encodeUInt32(value)); break; case 'int32' : case 'sInt32': offset = writeBytes(buffer, offset, codec.encodeSInt32(value)); break; case 'float': writeBytes(buffer, offset, codec.encodeFloat(value)); offset += 4; break; case 'double': writeBytes(buffer, offset, codec.encodeDouble(value)); offset += 8; break; case 'string': var length = codec.byteLength(value); //Encode length offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); //write string codec.encodeStr(buffer, offset, value); offset += length; break; default : if(!!protos.__messages[type]){ //Use a tmp buffer to build an internal msg var tmpBuffer = new ArrayBuffer(codec.byteLength(JSON.stringify(value))); var length = 0; length = encodeMsg(tmpBuffer, length, protos.__messages[type], value); //Encode length offset = writeBytes(buffer, offset, codec.encodeUInt32(length)); //contact the object for(var i = 0; i < length; i++){ buffer[offset] = tmpBuffer[i]; offset++; } } break; } return offset; } /** * Encode reapeated properties, simple msg and object are decode differented */ function encodeArray(array, proto, offset, buffer, protos){ var i = 0; if(util.isSimpleType(proto.type)){ offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length)); for(i = 0; i < array.length; i++){ offset = encodeProp(array[i], proto.type, offset, buffer); } }else{ for(i = 0; i < array.length; i++){ offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag)); offset = encodeProp(array[i], proto.type, offset, buffer, protos); } } return offset; } function writeBytes(buffer, offset, bytes){ for(var i = 0; i < bytes.length; i++, offset++){ buffer[offset] = bytes[i]; } return offset; } function encodeTag(type, tag){ var value = constant.TYPES[type]||2; return codec.encodeUInt32((tag<<3)|value); } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); /** * decoder module */ (function (exports, global){ var protobuf = exports; var MsgDecoder = exports.decoder = {}; var codec = protobuf.codec; var util = protobuf.util; var buffer; var offset = 0; MsgDecoder.init = function(protos){ this.protos = protos || {}; }; MsgDecoder.setProtos = function(protos){ if(!!protos){ this.protos = protos; } }; MsgDecoder.decode = function(route, buf){ var protos = this.protos[route]; buffer = buf; offset = 0; if(!!protos){ return decodeMsg({}, protos, buffer.length); } return null; }; function decodeMsg(msg, protos, length){ while(offset>3 }; } /** * Get tag head without move the offset */ function peekHead(){ var tag = codec.decodeUInt32(peekBytes()); return { type : tag&0x7, tag : tag>>3 }; } function decodeProp(type, protos){ switch(type){ case 'uInt32': return codec.decodeUInt32(getBytes()); case 'int32' : case 'sInt32' : return codec.decodeSInt32(getBytes()); case 'float' : var float = codec.decodeFloat(buffer, offset); offset += 4; return float; case 'double' : var double = codec.decodeDouble(buffer, offset); offset += 8; return double; case 'string' : var length = codec.decodeUInt32(getBytes()); var str = codec.decodeStr(buffer, offset, length); offset += length; return str; default : if(!!protos && !!protos.__messages[type]){ var length = codec.decodeUInt32(getBytes()); var msg = {}; decodeMsg(msg, protos.__messages[type], offset+length); return msg; } break; } } function decodeArray(array, type, protos){ if(util.isSimpleType(type)){ var length = codec.decodeUInt32(getBytes()); for(var i = 0; i < length; i++){ array.push(decodeProp(type)); } }else{ array.push(decodeProp(type, protos)); } } function getBytes(flag){ var bytes = []; var pos = offset; flag = flag || false; var b; do{ b = buffer[pos]; bytes.push(b); pos++; }while(b >= 128); if(!flag){ offset = pos; } return bytes; } function peekBytes(){ return getBytes(true); } })('undefined' !== typeof protobuf ? protobuf : module.exports, this); }); require.register("pomelonode-pomelo-jsclient-websocket/lib/pomelo-client.js", function(exports, require, module){ (function() { var JS_WS_CLIENT_TYPE = 'js-websocket'; var JS_WS_CLIENT_VERSION = '0.0.1'; var Protocol = window.Protocol; var Package = Protocol.Package; var Message = Protocol.Message; var EventEmitter = window.EventEmitter; var RES_OK = 200; var RES_FAIL = 500; var RES_OLD_CLIENT = 501; if (typeof Object.create !== 'function') { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; } var root = window; var pomelo = Object.create(EventEmitter.prototype); // object extend from object root.pomelo = pomelo; var socket = null; var reqId = 0; var callbacks = {}; var handlers = {}; //Map from request id to route var routeMap = {}; var heartbeatInterval = 0; var heartbeatTimeout = 0; var nextHeartbeatTimeout = 0; var gapThreshold = 100; // heartbeat gap threashold var heartbeatId = null; var heartbeatTimeoutId = null; var handshakeCallback = null; var handshakeBuffer = { 'sys': { type: JS_WS_CLIENT_TYPE, version: JS_WS_CLIENT_VERSION }, 'user': { } }; var initCallback = null; pomelo.init = function(params, cb){ initCallback = cb; var host = params.host; var port = params.port; var url = 'wss://' + host; if(port) { url += ':' + port; } handshakeBuffer.user = params.user; handshakeCallback = params.handshakeCallback; initWebSocket(url, cb); }; var initWebSocket = function(url,cb) { console.log('connect to ' + url); var onopen = function(event){ var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer))); send(obj); }; var onmessage = function(event) { processPackage(Package.decode(event.data), cb); // new package arrived, update the heartbeat timeout if(heartbeatTimeout) { nextHeartbeatTimeout = Date.now() + heartbeatTimeout; } }; var onerror = function(event) { pomelo.emit('io-error', event); console.error('socket error: ', event); }; var onclose = function(event){ pomelo.emit('close',event); console.error('socket close: ', event); }; socket = new WebSocket(url); socket.binaryType = 'arraybuffer'; socket.onopen = onopen; socket.onmessage = onmessage; socket.onerror = onerror; socket.onclose = onclose; }; pomelo.disconnect = function() { if(socket) { if(socket.disconnect) socket.disconnect(); if(socket.close) socket.close(); console.log('disconnect'); socket = null; } if(heartbeatId) { clearTimeout(heartbeatId); heartbeatId = null; } if(heartbeatTimeoutId) { clearTimeout(heartbeatTimeoutId); heartbeatTimeoutId = null; } }; pomelo.request = function(route, msg, cb) { if(arguments.length === 2 && typeof msg === 'function') { cb = msg; msg = {}; } else { msg = msg || {}; } route = route || msg.route; if(!route) { return; } reqId++; sendMessage(reqId, route, msg); callbacks[reqId] = cb; routeMap[reqId] = route; }; pomelo.notify = function(route, msg) { msg = msg || {}; sendMessage(0, route, msg); }; var sendMessage = function(reqId, route, msg) { var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; //compress message by protobuf var protos = !!pomelo.data.protos?pomelo.data.protos.client:{}; if(!!protos[route]){ msg = protobuf.encode(route, msg); }else{ msg = Protocol.strencode(JSON.stringify(msg)); } var compressRoute = 0; if(pomelo.dict && pomelo.dict[route]){ route = pomelo.dict[route]; compressRoute = 1; } msg = Message.encode(reqId, type, compressRoute, route, msg); var packet = Package.encode(Package.TYPE_DATA, msg); send(packet); }; var send = function(packet){ socket.send(packet.buffer); }; var handler = {}; var heartbeat = function(data) { if(!heartbeatInterval) { // no heartbeat return; } var obj = Package.encode(Package.TYPE_HEARTBEAT); if(heartbeatTimeoutId) { clearTimeout(heartbeatTimeoutId); heartbeatTimeoutId = null; } if(heartbeatId) { // already in a heartbeat interval return; } heartbeatId = setTimeout(function() { heartbeatId = null; send(obj); nextHeartbeatTimeout = Date.now() + heartbeatTimeout; heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout); }, heartbeatInterval); }; var heartbeatTimeoutCb = function() { var gap = nextHeartbeatTimeout - Date.now(); if(gap > gapThreshold) { heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap); } else { console.error('server heartbeat timeout'); pomelo.emit('heartbeat timeout'); pomelo.disconnect(); } }; var handshake = function(data){ data = JSON.parse(Protocol.strdecode(data)); if(data.code === RES_OLD_CLIENT) { pomelo.emit('error', 'client version not fullfill'); return; } if(data.code !== RES_OK) { pomelo.emit('error', 'handshake fail'); return; } handshakeInit(data); var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK); send(obj); if(initCallback) { initCallback(socket); initCallback = null; } }; var onData = function(data){ //probuff decode var msg = Message.decode(data); if(msg.id > 0){ msg.route = routeMap[msg.id]; delete routeMap[msg.id]; if(!msg.route){ return; } } msg.body = deCompose(msg); processMessage(pomelo, msg); }; var onKick = function(data) { pomelo.emit('onKick'); }; handlers[Package.TYPE_HANDSHAKE] = handshake; handlers[Package.TYPE_HEARTBEAT] = heartbeat; handlers[Package.TYPE_DATA] = onData; handlers[Package.TYPE_KICK] = onKick; var processPackage = function(msg) { handlers[msg.type](msg.body); }; var processMessage = function(pomelo, msg) { if(!msg.id) { // server push message pomelo.emit(msg.route, msg.body); return; } //if have a id then find the callback function with the request var cb = callbacks[msg.id]; delete callbacks[msg.id]; if(typeof cb !== 'function') { return; } cb(msg.body); return; }; var processMessageBatch = function(pomelo, msgs) { for(var i=0, l=msgs.length; i 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { if ('function' !== typeof listener) { throw new Error('.once only takes instances of Function'); } var self = this; function g() { self.removeListener(type, g); listener.apply(this, arguments); }; g.listener = listener; self.on(type, g); return this; }; EventEmitter.prototype.removeListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); } // does not use listeners(), so no side effect of creating _events[type] if (!this._events || !this._events[type]) return this; var list = this._events[type]; if (isArray(list)) { var position = -1; for (var i = 0, length = list.length; i < length; i++) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } if (position < 0) return this; list.splice(position, 1); } else if (list === listener || (list.listener && list.listener === listener)) { delete this._events[type]; } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { this._events = {}; return this; } var events = this._events && this._events[type]; if (!events) return this; if (isArray(events)) { events.splice(0); } else { this._events[type] = null; } return this; }; EventEmitter.prototype.listeners = function(type) { if (!this._events) this._events = {}; if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; } })(); (function (exports, global) { var Protocol = exports; var HEADER = 5; var Message = function(id,route,body){ this.id = id; this.route = route; this.body = body; }; /** * *pomele client encode * id message id; * route message route * msg message body * socketio current support string * */ Protocol.encode = function(id,route,msg){ var msgStr = JSON.stringify(msg); if (route.length>255) { throw new Error('route maxlength is overflow'); } var byteArray = new Uint16Array(HEADER + route.length + msgStr.length); var index = 0; byteArray[index++] = (id>>24) & 0xFF; byteArray[index++] = (id>>16) & 0xFF; byteArray[index++] = (id>>8) & 0xFF; byteArray[index++] = id & 0xFF; byteArray[index++] = route.length & 0xFF; for(var i = 0;i>>0; var routeLen = buf[HEADER-1]; var route = bt2Str(buf,HEADER, routeLen+HEADER); var body = bt2Str(buf,routeLen+HEADER,buf.length); return new Message(id,route,body); }; var bt2Str = function(byteArray,start,end) { var result = ""; for(var i = start; i < byteArray.length && i 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { if ('function' !== typeof listener) { throw new Error('.once only takes instances of Function'); } var self = this; function g() { self.removeListener(type, g); listener.apply(this, arguments); }; g.listener = listener; self.on(type, g); return this; }; EventEmitter.prototype.removeListener = function(type, listener) { if ('function' !== typeof listener) { throw new Error('removeListener only takes instances of Function'); } // does not use listeners(), so no side effect of creating _events[type] if (!this._events || !this._events[type]) return this; var list = this._events[type]; if (isArray(list)) { var position = -1; for (var i = 0, length = list.length; i < length; i++) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } if (position < 0) return this; list.splice(position, 1); } else if (list === listener || (list.listener && list.listener === listener)) { delete this._events[type]; } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { this._events = {}; return this; } var events = this._events && this._events[type]; if (!events) return this; if (isArray(events)) { events.splice(0); } else { this._events[type] = null; } return this; }; EventEmitter.prototype.listeners = function(type) { if (!this._events) this._events = {}; if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; } })(); (function (exports, global) { var Protocol = exports; var HEADER = 5; var Message = function(id,route,body){ this.id = id; this.route = route; this.body = body; }; /** * *pomele client encode * id message id; * route message route * msg message body * socketio current support string * */ Protocol.encode = function(id,route,msg){ var msgStr = JSON.stringify(msg); if (route.length>255) { throw new Error('route maxlength is overflow'); } var byteArray = new Uint16Array(HEADER + route.length + msgStr.length); var index = 0; byteArray[index++] = (id>>24) & 0xFF; byteArray[index++] = (id>>16) & 0xFF; byteArray[index++] = (id>>8) & 0xFF; byteArray[index++] = id & 0xFF; byteArray[index++] = route.length & 0xFF; for(var i = 0;i>>0; var routeLen = buf[HEADER-1]; var route = bt2Str(buf,HEADER, routeLen+HEADER); var body = bt2Str(buf,routeLen+HEADER,buf.length); return new Message(id,route,body); }; var bt2Str = function(byteArray,start,end) { var result = ""; for(var i = start; i < byteArray.length && i=31}function i(){var t=arguments,r=this.useColors;if(t[0]=(r?"%c":"")+this.namespace+(r?" %c":" ")+t[0]+(r?"%c ":" ")+"+"+e.humanize(this.diff),!r)return t;var n="color: "+this.color;t=[t[0],n,"color: inherit"].concat(Array.prototype.slice.call(t,1));var o=0,i=0;return t[0].replace(/%[a-z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,n),t}function s(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(t){}}function c(){try{return e.storage.debug}catch(t){}if("undefined"!=typeof n&&"env"in n)return n.env.DEBUG}function u(){try{return window.localStorage}catch(t){}}e=t.exports=r(5),e.log=s,e.formatArgs=i,e.save=a,e.load=c,e.useColors=o,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:u(),e.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],e.formatters.j=function(t){try{return JSON.stringify(t)}catch(t){return"[UnexpectedJSONParseError]: "+t.message}},e.enable(c())}).call(e,r(4))},function(t,e){function r(){throw new Error("setTimeout has not been defined")}function n(){throw new Error("clearTimeout has not been defined")}function o(t){if(h===setTimeout)return setTimeout(t,0);if((h===r||!h)&&setTimeout)return h=setTimeout,setTimeout(t,0);try{return h(t,0)}catch(e){try{return h.call(null,t,0)}catch(e){return h.call(this,t,0)}}}function i(t){if(p===clearTimeout)return clearTimeout(t);if((p===n||!p)&&clearTimeout)return p=clearTimeout,clearTimeout(t);try{return p(t)}catch(e){try{return p.call(null,t)}catch(e){return p.call(this,t)}}}function s(){y&&l&&(y=!1,l.length?d=l.concat(d):g=-1,d.length&&a())}function a(){if(!y){var t=o(s);y=!0;for(var e=d.length;e;){for(l=d,d=[];++g1)for(var r=1;r1e4)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),n=(e[2]||"ms").toLowerCase();switch(n){case"years":case"year":case"yrs":case"yr":case"y":return r*h;case"days":case"day":case"d":return r*u;case"hours":case"hour":case"hrs":case"hr":case"h":return r*c;case"minutes":case"minute":case"mins":case"min":case"m":return r*a;case"seconds":case"second":case"secs":case"sec":case"s":return r*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}function n(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,r){if(!(t0)return r(t);if("number"===i&&isNaN(t)===!1)return e.long?o(t):n(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))}},function(t,e,r){function n(){}function o(t){var r="",n=!1;return r+=t.type,e.BINARY_EVENT!=t.type&&e.BINARY_ACK!=t.type||(r+=t.attachments,r+="-"),t.nsp&&"/"!=t.nsp&&(n=!0,r+=t.nsp),null!=t.id&&(n&&(r+=",",n=!1),r+=t.id),null!=t.data&&(n&&(r+=","),r+=f.stringify(t.data)),p("encoded %j as %s",t,r),r}function i(t,e){function r(t){var r=d.deconstructPacket(t),n=o(r.packet),i=r.buffers;i.unshift(n),e(i)}d.removeBlobs(t,r)}function s(){this.reconstructor=null}function a(t){var r={},n=0;if(r.type=Number(t.charAt(0)),null==e.types[r.type])return h();if(e.BINARY_EVENT==r.type||e.BINARY_ACK==r.type){for(var o="";"-"!=t.charAt(++n)&&(o+=t.charAt(n),n!=t.length););if(o!=Number(o)||"-"!=t.charAt(n))throw new Error("Illegal attachments");r.attachments=Number(o)}if("/"==t.charAt(n+1))for(r.nsp="";++n;){var i=t.charAt(n);if(","==i)break;if(r.nsp+=i,n==t.length)break}else r.nsp="/";var s=t.charAt(n+1);if(""!==s&&Number(s)==s){for(r.id="";++n;){var i=t.charAt(n);if(null==i||Number(i)!=i){--n;break}if(r.id+=t.charAt(n),n==t.length)break}r.id=Number(r.id)}return t.charAt(++n)&&(r=c(r,t.substr(n))),p("decoded %s as %j",t,r),r}function c(t,e){try{t.data=f.parse(e)}catch(t){return h()}return t}function u(t){this.reconPack=t,this.buffers=[]}function h(t){return{type:e.ERROR,data:"parser error"}}var p=r(8)("socket.io-parser"),f=r(11),l=r(13),d=r(14),y=r(16);e.protocol=4,e.types=["CONNECT","DISCONNECT","EVENT","ACK","ERROR","BINARY_EVENT","BINARY_ACK"],e.CONNECT=0,e.DISCONNECT=1,e.EVENT=2,e.ACK=3,e.ERROR=4,e.BINARY_EVENT=5,e.BINARY_ACK=6,e.Encoder=n,e.Decoder=s,n.prototype.encode=function(t,r){if(p("encoding packet %j",t),e.BINARY_EVENT==t.type||e.BINARY_ACK==t.type)i(t,r);else{var n=o(t);r([n])}},l(s.prototype),s.prototype.add=function(t){var r;if("string"==typeof t)r=a(t),e.BINARY_EVENT==r.type||e.BINARY_ACK==r.type?(this.reconstructor=new u(r),0===this.reconstructor.reconPack.attachments&&this.emit("decoded",r)):this.emit("decoded",r);else{if(!y(t)&&!t.base64)throw new Error("Unknown type: "+t);if(!this.reconstructor)throw new Error("got binary data when not reconstructing a packet");r=this.reconstructor.takeBinaryData(t),r&&(this.reconstructor=null,this.emit("decoded",r))}},s.prototype.destroy=function(){this.reconstructor&&this.reconstructor.finishedReconstruction()},u.prototype.takeBinaryData=function(t){if(this.buffers.push(t),this.buffers.length==this.reconPack.attachments){var e=d.reconstructPacket(this.reconPack,this.buffers);return this.finishedReconstruction(),e}return null},u.prototype.finishedReconstruction=function(){this.reconPack=null,this.buffers=[]}},function(t,e,r){function n(){return"WebkitAppearance"in document.documentElement.style||window.console&&(console.firebug||console.exception&&console.table)||navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31}function o(){var t=arguments,r=this.useColors;if(t[0]=(r?"%c":"")+this.namespace+(r?" %c":" ")+t[0]+(r?"%c ":" ")+"+"+e.humanize(this.diff),!r)return t;var n="color: "+this.color;t=[t[0],n,"color: inherit"].concat(Array.prototype.slice.call(t,1));var o=0,i=0;return t[0].replace(/%[a-z%]/g,function(t){"%%"!==t&&(o++,"%c"===t&&(i=o))}),t.splice(i,0,n),t}function i(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function s(t){try{null==t?e.storage.removeItem("debug"):e.storage.debug=t}catch(t){}}function a(){var t;try{t=e.storage.debug}catch(t){}return t}function c(){try{return window.localStorage}catch(t){}}e=t.exports=r(9),e.log=i,e.formatArgs=o,e.save=s,e.load=a,e.useColors=n,e.storage="undefined"!=typeof chrome&&"undefined"!=typeof chrome.storage?chrome.storage.local:c(),e.colors=["lightseagreen","forestgreen","goldenrod","dodgerblue","darkorchid","crimson"],e.formatters.j=function(t){return JSON.stringify(t)},e.enable(a())},function(t,e,r){function n(){return e.colors[h++%e.colors.length]}function o(t){function r(){}function o(){var t=o,r=+new Date,i=r-(u||r);t.diff=i,t.prev=u,t.curr=r,u=r,null==t.useColors&&(t.useColors=e.useColors()),null==t.color&&t.useColors&&(t.color=n());var s=Array.prototype.slice.call(arguments);s[0]=e.coerce(s[0]),"string"!=typeof s[0]&&(s=["%o"].concat(s));var a=0;s[0]=s[0].replace(/%([a-z%])/g,function(r,n){if("%%"===r)return r;a++;var o=e.formatters[n];if("function"==typeof o){var i=s[a];r=o.call(t,i),s.splice(a,1),a--}return r}),"function"==typeof e.formatArgs&&(s=e.formatArgs.apply(t,s));var c=o.log||e.log||console.log.bind(console);c.apply(t,s)}r.enabled=!1,o.enabled=!0;var i=e.enabled(t)?o:r;return i.namespace=t,i}function i(t){e.save(t);for(var r=(t||"").split(/[\s,]+/),n=r.length,o=0;o1e4)){var e=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),n=(e[2]||"ms").toLowerCase();switch(n){case"years":case"year":case"yrs":case"yr":case"y":return r*h;case"days":case"day":case"d":return r*u;case"hours":case"hour":case"hrs":case"hr":case"h":return r*c;case"minutes":case"minute":case"mins":case"min":case"m":return r*a;case"seconds":case"second":case"secs":case"sec":case"s":return r*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r}}}}function n(t){return t>=u?Math.round(t/u)+"d":t>=c?Math.round(t/c)+"h":t>=a?Math.round(t/a)+"m":t>=s?Math.round(t/s)+"s":t+"ms"}function o(t){return i(t,u,"day")||i(t,c,"hour")||i(t,a,"minute")||i(t,s,"second")||t+" ms"}function i(t,e,r){if(!(t1)))/4)-T((t-1901+e)/100)+T((t-1601+e)/400)};if((d=m.hasOwnProperty)||(d=function(t){var e,r={};return(r.__proto__=null,r.__proto__={toString:1},r).toString!=v?d=function(t){var e=this.__proto__,r=t in(this.__proto__=null,this);return this.__proto__=e,r}:(e=r.constructor,d=function(t){var r=(this.constructor||e).prototype;return t in this&&!(t in r&&this[t]===r[t])}),r=null,d.call(this,t)}),y=function(t,e){var r,n,o,i=0;(r=function(){this.valueOf=0}).prototype.valueOf=0,n=new r;for(o in n)d.call(n,o)&&i++;return r=n=null,i?y=2==i?function(t,e){var r,n={},o=v.call(t)==w;for(r in t)o&&"prototype"==r||d.call(n,r)||!(n[r]=1)||!d.call(t,r)||e(r)}:function(t,e){var r,n,o=v.call(t)==w;for(r in t)o&&"prototype"==r||!d.call(t,r)||(n="constructor"===r)||e(r);(n||d.call(t,r="constructor"))&&e(r)}:(n=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"],y=function(t,e){var r,o,i=v.call(t)==w,a=!i&&"function"!=typeof t.constructor&&s[typeof t.hasOwnProperty]&&t.hasOwnProperty||d;for(r in t)i&&"prototype"==r||!a.call(t,r)||e(r);for(o=n.length;r=n[--o];a.call(t,r)&&e(r));}),y(t,e)},!r("json-stringify")){var N={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"},j="000000",O=function(t,e){return(j+(e||0)).slice(-t)},P="\\u00",R=function(t){for(var e='"',r=0,n=t.length,o=!S||n>10,i=o&&(S?t.split(""):t);r-1/0&&a<1/0){if(_){for(f=T(a/864e5),u=T(f/365.2425)+1970-1;_(u+1,0)<=f;u++);for(h=T((f-_(u,0))/30.42);_(u,h+1)<=f;h++);f=1+f-_(u,h),l=(a%864e5+864e5)%864e5,m=T(l/36e5)%24,b=T(l/6e4)%60,w=T(l/1e3)%60,S=l%1e3}else u=a.getUTCFullYear(),h=a.getUTCMonth(),f=a.getUTCDate(),m=a.getUTCHours(),b=a.getUTCMinutes(),w=a.getUTCSeconds(),S=a.getUTCMilliseconds();a=(u<=0||u>=1e4?(u<0?"-":"+")+O(6,u<0?-u:u):O(4,u))+"-"+O(2,h+1)+"-"+O(2,f)+"T"+O(2,m)+":"+O(2,b)+":"+O(2,w)+"."+O(3,S)+"Z"}else a=null;if(r&&(a=r.call(e,t,a)),null===a)return"null";if(c=v.call(a),c==B)return""+a;if(c==x)return a>-1/0&&a<1/0?""+a:"null";if(c==A)return R(""+a);if("object"==typeof a){for(P=s.length;P--;)if(s[P]===a)throw p();if(s.push(a),E=[],q=i,i+=o,c==C){for(j=0,P=a.length;j0)for(n="",r>10&&(r=10);n.length=48&&o<=57||o>=97&&o<=102||o>=65&&o<=70||I();t+=M("0x"+i.slice(e,q));break;default:I()}else{if(34==o)break;for(o=i.charCodeAt(q),e=q;o>=32&&92!=o&&34!=o;)o=i.charCodeAt(++q);t+=i.slice(e,q)}if(34==i.charCodeAt(q))return q++,t;I();default:if(e=q,45==o&&(n=!0,o=i.charCodeAt(++q)),o>=48&&o<=57){for(48==o&&(o=i.charCodeAt(q+1),o>=48&&o<=57)&&I(),n=!1;q=48&&o<=57);q++);if(46==i.charCodeAt(q)){for(r=++q;r=48&&o<=57);r++);r==q&&I(),q=r}if(o=i.charCodeAt(q),101==o||69==o){for(o=i.charCodeAt(++q),43!=o&&45!=o||q++,r=q;r=48&&o<=57);r++);r==q&&I(),q=r}return+i.slice(e,q)}if(n&&I(),"true"==i.slice(q,q+4))return q+=4,!0;if("false"==i.slice(q,q+5))return q+=5,!1;if("null"==i.slice(q,q+4))return q+=4,null;I()}return"$"},z=function(t){var e,r;if("$"==t&&I(),"string"==typeof t){if("@"==(S?t.charAt(0):t[0]))return t.slice(1);if("["==t){for(e=[];t=H(),"]"!=t;r||(r=!0))r&&(","==t?(t=H(),"]"==t&&I()):I()),","==t&&I(),e.push(z(t));return e}if("{"==t){for(e={};t=H(),"}"!=t;r||(r=!0))r&&(","==t?(t=H(),"}"==t&&I()):I()),","!=t&&"string"==typeof t&&"@"==(S?t.charAt(0):t[0])&&":"==H()||I(),e[t.slice(1)]=z(H());return e}I()}return t},J=function(t,e,r){var n=X(t,e,r);n===g?delete t[e]:t[e]=n},X=function(t,e,r){var n,o=t[e];if("object"==typeof o&&o)if(v.call(o)==C)for(n=o.length;n--;)J(o,n,r);else y(o,function(t){J(o,t,r)});return r.call(t,e,o)};e.parse=function(t,e){var r,n;return q=0,U=""+t,r=z(H()),"$"!=H()&&I(),q=U=null,e&&v.call(e)==w?X((n={},n[""]=r,n),"",e):r}}}return e.runInContext=o,e}var i="function"==typeof n&&n.amd,s={function:!0,object:!0},a=s[typeof e]&&e&&!e.nodeType&&e,c=s[typeof window]&&window||this,u=a&&s[typeof t]&&t&&!t.nodeType&&"object"==typeof r&&r;if(!u||u.global!==u&&u.window!==u&&u.self!==u||(c=u),a&&!i)o(c,a);else{var h=c.JSON,p=c.JSON3,f=!1,l=o(c,c.JSON3={noConflict:function(){return f||(f=!0,c.JSON=h,c.JSON3=p,h=p=null),l}});c.JSON={parse:l.parse,stringify:l.stringify}}i&&n(function(){return l})}).call(this)}).call(e,r(12)(t),function(){return this}())},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children=[],t.webpackPolyfill=1),t}},function(t,e){function r(t){if(t)return n(t)}function n(t){for(var e in r.prototype)t[e]=r.prototype[e];return t}t.exports=r,r.prototype.on=r.prototype.addEventListener=function(t,e){return this._callbacks=this._callbacks||{},(this._callbacks[t]=this._callbacks[t]||[]).push(e),this},r.prototype.once=function(t,e){function r(){n.off(t,r),e.apply(this,arguments)}var n=this;return this._callbacks=this._callbacks||{},r.fn=e,this.on(t,r),this},r.prototype.off=r.prototype.removeListener=r.prototype.removeAllListeners=r.prototype.removeEventListener=function(t,e){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var r=this._callbacks[t];if(!r)return this;if(1==arguments.length)return delete this._callbacks[t],this;for(var n,o=0;o0&&!this.encoding){var t=this.packetBuffer.shift();this.packet(t)}},n.prototype.cleanup=function(){p("cleanup");for(var t=this.subs.length,e=0;e=this._reconnectionAttempts)p("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var e=this.backoff.duration();p("will wait %dms before reconnect attempt",e),this.reconnecting=!0;var r=setTimeout(function(){t.skipReconnect||(p("attempting reconnect"),t.emitAll("reconnect_attempt",t.backoff.attempts),t.emitAll("reconnecting",t.backoff.attempts),t.skipReconnect||t.open(function(e){e?(p("reconnect attempt error"),t.reconnecting=!1,t.reconnect(),t.emitAll("reconnect_error",e.data)):(p("reconnect success"),t.onreconnect())}))},e);this.subs.push({destroy:function(){clearTimeout(r)}})}},n.prototype.onreconnect=function(){var t=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",t)}},function(t,e,r){t.exports=r(19)},function(t,e,r){t.exports=r(20),t.exports.parser=r(27)},function(t,e,r){(function(e){function n(t,r){if(!(this instanceof n))return new n(t,r);r=r||{},t&&"object"==typeof t&&(r=t,t=null),t?(t=h(t),r.hostname=t.host,r.secure="https"===t.protocol||"wss"===t.protocol,r.port=t.port,t.query&&(r.query=t.query)):r.host&&(r.hostname=h(r.host).host), this.secure=null!=r.secure?r.secure:e.location&&"https:"===location.protocol,r.hostname&&!r.port&&(r.port=this.secure?"443":"80"),this.agent=r.agent||!1,this.hostname=r.hostname||(e.location?location.hostname:"localhost"),this.port=r.port||(e.location&&location.port?location.port:this.secure?443:80),this.query=r.query||{},"string"==typeof this.query&&(this.query=f.decode(this.query)),this.upgrade=!1!==r.upgrade,this.path=(r.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!r.forceJSONP,this.jsonp=!1!==r.jsonp,this.forceBase64=!!r.forceBase64,this.enablesXDR=!!r.enablesXDR,this.timestampParam=r.timestampParam||"t",this.timestampRequests=r.timestampRequests,this.transports=r.transports||["polling","websocket"],this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=r.policyPort||843,this.rememberUpgrade=r.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=r.onlyBinaryUpgrades,this.perMessageDeflate=!1!==r.perMessageDeflate&&(r.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=r.pfx||null,this.key=r.key||null,this.passphrase=r.passphrase||null,this.cert=r.cert||null,this.ca=r.ca||null,this.ciphers=r.ciphers||null,this.rejectUnauthorized=void 0===r.rejectUnauthorized?null:r.rejectUnauthorized,this.forceNode=!!r.forceNode;var o="object"==typeof e&&e;o.global===o&&(r.extraHeaders&&Object.keys(r.extraHeaders).length>0&&(this.extraHeaders=r.extraHeaders),r.localAddress&&(this.localAddress=r.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}function o(t){var e={};for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}var i=r(21),s=r(35),a=r(3)("engine.io-client:socket"),c=r(42),u=r(27),h=r(2),p=r(43),f=r(36);t.exports=n,n.priorWebsocketSuccess=!1,s(n.prototype),n.protocol=u.protocol,n.Socket=n,n.Transport=r(26),n.transports=r(21),n.parser=r(27),n.prototype.createTransport=function(t){a('creating transport "%s"',t);var e=o(this.query);e.EIO=u.protocol,e.transport=t,this.id&&(e.sid=this.id);var r=new i[t]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:e,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized,perMessageDeflate:this.perMessageDeflate,extraHeaders:this.extraHeaders,forceNode:this.forceNode,localAddress:this.localAddress});return r},n.prototype.open=function(){var t;if(this.rememberUpgrade&&n.priorWebsocketSuccess&&this.transports.indexOf("websocket")!==-1)t="websocket";else{if(0===this.transports.length){var e=this;return void setTimeout(function(){e.emit("error","No transports available")},0)}t=this.transports[0]}this.readyState="opening";try{t=this.createTransport(t)}catch(t){return this.transports.shift(),void this.open()}t.open(),this.setTransport(t)},n.prototype.setTransport=function(t){a("setting transport %s",t.name);var e=this;this.transport&&(a("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=t,t.on("drain",function(){e.onDrain()}).on("packet",function(t){e.onPacket(t)}).on("error",function(t){e.onError(t)}).on("close",function(){e.onClose("transport close")})},n.prototype.probe=function(t){function e(){if(f.onlyBinaryUpgrades){var e=!this.supportsBinary&&f.transport.supportsBinary;p=p||e}p||(a('probe transport "%s" opened',t),h.send([{type:"ping",data:"probe"}]),h.once("packet",function(e){if(!p)if("pong"===e.type&&"probe"===e.data){if(a('probe transport "%s" pong',t),f.upgrading=!0,f.emit("upgrading",h),!h)return;n.priorWebsocketSuccess="websocket"===h.name,a('pausing current transport "%s"',f.transport.name),f.transport.pause(function(){p||"closed"!==f.readyState&&(a("changing transport and sending upgrade packet"),u(),f.setTransport(h),h.send([{type:"upgrade"}]),f.emit("upgrade",h),h=null,f.upgrading=!1,f.flush())})}else{a('probe transport "%s" failed',t);var r=new Error("probe error");r.transport=h.name,f.emit("upgradeError",r)}}))}function r(){p||(p=!0,u(),h.close(),h=null)}function o(e){var n=new Error("probe error: "+e);n.transport=h.name,r(),a('probe transport "%s" failed because of error: %s',t,e),f.emit("upgradeError",n)}function i(){o("transport closed")}function s(){o("socket closed")}function c(t){h&&t.name!==h.name&&(a('"%s" works - aborting "%s"',t.name,h.name),r())}function u(){h.removeListener("open",e),h.removeListener("error",o),h.removeListener("close",i),f.removeListener("close",s),f.removeListener("upgrading",c)}a('probing transport "%s"',t);var h=this.createTransport(t,{probe:1}),p=!1,f=this;n.priorWebsocketSuccess=!1,h.once("open",e),h.once("error",o),h.once("close",i),this.once("close",s),this.once("upgrading",c),h.open()},n.prototype.onOpen=function(){if(a("socket open"),this.readyState="open",n.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){a("starting upgrade probes");for(var t=0,e=this.upgrades.length;t1?{type:b[o],data:t.substring(1)}:{type:b[o]}:w}var i=new Uint8Array(t),o=i[0],s=f(t,1);return k&&"blob"===r&&(s=new k([s])),{type:b[o],data:s}},e.decodeBase64Packet=function(t,e){var r=b[t.charAt(0)];if(!u)return{type:r,data:{base64:!0,data:t.substr(1)}};var n=u.decode(t.substr(1));return"blob"===e&&k&&(n=new k([n])),{type:r,data:n}},e.encodePayload=function(t,r,n){function o(t){return t.length+":"+t}function i(t,n){e.encodePacket(t,!!s&&r,!0,function(t){n(null,o(t))})}"function"==typeof r&&(n=r,r=null);var s=p(t);return r&&s?k&&!m?e.encodePayloadAsBlob(t,n):e.encodePayloadAsArrayBuffer(t,n):t.length?void c(t,i,function(t,e){return n(e.join(""))}):n("0:")},e.decodePayload=function(t,r,n){if("string"!=typeof t)return e.decodePayloadAsBinary(t,r,n);"function"==typeof r&&(n=r,r=null);var o;if(""==t)return n(w,0,1);for(var i,s,a="",c=0,u=t.length;c0;){for(var a=new Uint8Array(o),c=0===a[0],u="",h=1;255!=a[h];h++){if(u.length>310){s=!0;break}u+=a[h]}if(s)return n(w,0,1);o=f(o,2+u.length),u=parseInt(u);var p=f(o,0,u);if(c)try{p=String.fromCharCode.apply(null,new Uint8Array(p))}catch(t){var l=new Uint8Array(p);p="";for(var h=0;hn&&(r=n),e>=n||e>=r||0===n)return new ArrayBuffer(0);for(var o=new Uint8Array(t),i=new Uint8Array(r-e),s=e,a=0;s=55296&&e<=56319&&o65535&&(e-=65536,o+=b(e>>>10&1023|55296),e=56320|1023&e),o+=b(e);return o}function c(t,e){return b(t>>e&63|128)}function u(t){if(0==(4294967168&t))return b(t);var e="";return 0==(4294965248&t)?e=b(t>>6&31|192):0==(4294901760&t)?(e=b(t>>12&15|224),e+=c(t,6)):0==(4292870144&t)&&(e=b(t>>18&7|240),e+=c(t,12),e+=c(t,6)),e+=b(63&t|128)}function h(t){for(var e,r=s(t),n=r.length,o=-1,i="";++o=m)throw Error("Invalid byte index");var t=255&g[v];if(v++,128==(192&t))return 63&t;throw Error("Invalid continuation byte")}function f(){var t,e,r,n,o;if(v>m)throw Error("Invalid byte index");if(v==m)return!1;if(t=255&g[v],v++,0==(128&t))return t;if(192==(224&t)){var e=p();if(o=(31&t)<<6|e,o>=128)return o;throw Error("Invalid continuation byte")}if(224==(240&t)){if(e=p(),r=p(),o=(15&t)<<12|e<<6|r,o>=2048)return o;throw Error("Invalid continuation byte")}if(240==(248&t)&&(e=p(),r=p(),n=p(),o=(15&t)<<18|e<<12|r<<6|n,o>=65536&&o<=1114111))return o;throw Error("Invalid WTF-8 detected")}function l(t){g=s(t),m=g.length,v=0;for(var e,r=[];(e=f())!==!1;)r.push(e);return a(r)}var d="object"==typeof e&&e,y=("object"==typeof t&&t&&t.exports==d&&t,"object"==typeof o&&o);y.global!==y&&y.window!==y||(i=y);var g,m,v,b=String.fromCharCode,w={version:"1.0.0",encode:h,decode:l};n=function(){return w}.call(e,r,e,t),!(void 0!==n&&(t.exports=n))}(this)}).call(e,r(12)(t),function(){return this}())},function(t,e){!function(){"use strict";for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=new Uint8Array(256),n=0;n>2],i+=t[(3&n[r])<<4|n[r+1]>>4],i+=t[(15&n[r+1])<<2|n[r+2]>>6],i+=t[63&n[r+2]];return o%3===2?i=i.substring(0,i.length-1)+"=":o%3===1&&(i=i.substring(0,i.length-2)+"=="),i},e.decode=function(t){var e,n,o,i,s,a=.75*t.length,c=t.length,u=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var h=new ArrayBuffer(a),p=new Uint8Array(h);for(e=0;e>4,p[u++]=(15&o)<<4|i>>2,p[u++]=(3&i)<<6|63&s;return h}}()},function(t,e){(function(e){function r(t){for(var e=0;e0);return e}function n(t){var e=0;for(h=0;h';i=document.createElement(t)}catch(t){i=document.createElement("iframe"),i.name=o.iframeId,i.src="javascript:0"}i.id=o.iframeId,o.form.appendChild(i),o.iframe=i}var o=this;if(!this.form){var i,s=document.createElement("form"),a=document.createElement("textarea"),h=this.iframeId="eio_iframe_"+this.index;s.className="socketio",s.style.position="absolute",s.style.top="-1000px",s.style.left="-1000px",s.target=h,s.method="POST",s.setAttribute("accept-charset","utf-8"),a.name="d",s.appendChild(a),document.body.appendChild(s),this.form=s,this.area=a}this.form.action=this.uri(),n(),t=t.replace(u,"\\\n"),this.area.value=t.replace(c,"\\n");try{this.form.submit()}catch(t){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===o.iframe.readyState&&r(); }:this.iframe.onload=r}}).call(e,function(){return this}())},function(t,e,r){(function(e){function n(t){var e=t&&t.forceBase64;e&&(this.supportsBinary=!1),this.perMessageDeflate=t.perMessageDeflate,this.usingBrowserWebSocket=p&&!t.forceNode,this.usingBrowserWebSocket||(f=o),i.call(this,t)}var o,i=r(26),s=r(27),a=r(36),c=r(37),u=r(38),h=r(3)("engine.io-client:websocket"),p=e.WebSocket||e.MozWebSocket;if("undefined"==typeof window)try{o=r(41)}catch(t){}var f=p;f||"undefined"!=typeof window||(f=o),t.exports=n,c(n,i),n.prototype.name="websocket",n.prototype.supportsBinary=!0,n.prototype.doOpen=function(){if(this.check()){var t=this.uri(),e=void 0,r={agent:this.agent,perMessageDeflate:this.perMessageDeflate};r.pfx=this.pfx,r.key=this.key,r.passphrase=this.passphrase,r.cert=this.cert,r.ca=this.ca,r.ciphers=this.ciphers,r.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(r.headers=this.extraHeaders),this.localAddress&&(r.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket?new f(t):new f(t,e,r)}catch(t){return this.emit("error",t)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},n.prototype.addEventListeners=function(){var t=this;this.ws.onopen=function(){t.onOpen()},this.ws.onclose=function(){t.onClose()},this.ws.onmessage=function(e){t.onData(e.data)},this.ws.onerror=function(e){t.onError("websocket error",e)}},n.prototype.write=function(t){function r(){n.emit("flush"),setTimeout(function(){n.writable=!0,n.emit("drain")},0)}var n=this;this.writable=!1;for(var o=t.length,i=0,a=o;i0&&t.jitter<=1?t.jitter:0,this.attempts=0}t.exports=r,r.prototype.duration=function(){var t=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var e=Math.random(),r=Math.floor(e*this.jitter*t);t=0==(1&Math.floor(10*e))?t-r:t+r}return 0|Math.min(t,this.max)},r.prototype.reset=function(){this.attempts=0},r.prototype.setMin=function(t){this.ms=t},r.prototype.setMax=function(t){this.max=t},r.prototype.setJitter=function(t){this.jitter=t}}])}); ================================================ FILE: test/application.js ================================================ var app = require('../lib/application'); var pomelo = require('../'); var should = require('should'); var WAIT_TIME = 1000; var mockBase = process.cwd() + '/test'; describe('application test', function(){ afterEach(function() { app.state = 0; app.settings = {}; }); describe('#init', function() { it('should init the app instance', function() { app.init({base: mockBase}); app.state.should.equal(1); // magic number from application.js }); }); describe('#set and get', function() { it('should play the role of normal set and get', function() { should.not.exist(app.get('some undefined key')); var key = 'some defined key', value = 'some value'; app.set(key, value); value.should.equal(app.get(key)); }); it('should return the value if pass just one parameter to the set method', function() { var key = 'some defined key', value = 'some value'; should.not.exist(app.set(key)); app.set(key, value); value.should.equal(app.set(key)); }); }); describe("#enable and disable", function() { it('should play the role of enable and disable', function() { var key = 'some enable key'; app.enabled(key).should.be.false; app.disabled(key).should.be.true; app.enable(key); app.enabled(key).should.be.true; app.disabled(key).should.be.false; app.disable(key); app.enabled(key).should.be.false; app.disabled(key).should.be.true; }); }); describe("#compoent", function() { it('should load the component and fire their lifecircle callback by app.start, app.afterStart, app.stop', function(done) { var startCount = 0, afterStartCount = 0, stopCount = 0; var mockComponent = { start: function(cb) { console.log('start invoked'); startCount++; cb(); }, afterStart: function(cb) { console.log('afterStart invoked'); afterStartCount++; cb(); }, stop: function(force, cb) { console.log('stop invoked'); stopCount++; cb(); } }; app.init({base: mockBase}); app.load(mockComponent); app.start(function(err) { should.not.exist(err); }); setTimeout(function() { // wait for after start app.stop(false); setTimeout(function() { // wait for stop startCount.should.equal(1); afterStartCount.should.equal(1); stopCount.should.equal(1); done(); }, WAIT_TIME); }, WAIT_TIME); }); it('should access the component with a name by app.components.name after loaded', function() { var key1 = 'key1', comp1 = {content: 'some thing in comp1'}; var comp2 = {name: 'key2', content: 'some thing in comp2'}; var key3 = 'key3'; var comp3 = function() { return {content: 'some thing in comp3', name: key3}; }; app.init({base: mockBase}); app.load(key1, comp1); app.load(comp2); app.load(comp3); app.components.key1.should.eql(comp1); app.components.key2.should.eql(comp2); app.components.key3.should.eql(comp3()); }); it('should ignore duplicated components', function() { var key = 'key'; var comp1 = {content: 'some thing in comp1'}; var comp2 = {content: 'some thing in comp2'}; app.init({base: mockBase}); app.load(key, comp1); app.load(key, comp2); app.components[key].should.eql(comp1); app.components[key].should.not.eql(comp2); }); }); describe('#filter', function() { it('should add before filter and could fetch it later', function() { var filters = [ function() {console.error('filter1');}, function() {} ]; app.init({base: mockBase}); var i, l; for(i=0, l=filters.length; i 3000) { timeout = 3000; } _setTimeout(cb, timeout); }; var agent1 = { request: function(recordId, moduleId, msg, cb) { cb('chat-server-1'); }, idMap: { 'chat-server-1': { type: 'chat', id: 'chat-server-1' } } }; module.clientHandler(agent1, msg, function(err, result) { should.not.exist(err); should.exist(result.code); }); var agent2 = { request: function(recordId, moduleId, msg, cb) { cb(null); }, idMap: { 'chat-server-1': { type: 'chat', id: 'chat-server-1' } } }; module.clientHandler(agent2, msg, function(err, result) { should.not.exist(err); should.exist(result.code); result.code.should.eql('remained'); done(); }); }); it('should execute stop command', function(done) { var msg1 = {signal: 'stop', ids: ['chat-server-1']}; var msg2 = {signal: 'stop', ids:[]}; var agent = { notifyById: function(serverId, moduleId, msg) { }, notifyAll: function(moduleId, msg) { } }; module.clientHandler(agent, msg1, function(err, result) { result.status.should.eql('part'); }); module.clientHandler(agent, msg2, function(err, result) { result.status.should.eql('all'); done(); }); }); it('should execute list command', function() { var msg = {signal: 'list'}; var agent = { request: function(recordId, moduleId, msg, cb) { cb({serverId: 'chat-server-1', body: {'server':{}}}); }, idMap: { 'chat-server-1': { type: 'chat', id: 'chat-server-1' } } }; module.clientHandler(agent, msg, function(err, result) { should.exist(result.msg); }); }); it('should execute add command', function() { var msg1 = {signal: 'add', args: ['host=127.0.0.1', 'port=88888', 'clusterCount=2']}; var msg2 = {signal: 'add', args: ['host=127.0.0.1', 'port=88888', 'id=chat-server-1', 'serverType=chat']}; var agent = {}; module.clientHandler(agent, msg1, function(err, result) { should.not.exist(err); result.length.should.eql(0); }); module.clientHandler(agent, msg2, function(err, result) { result.status.should.eql('ok'); }); }); it('should execute blacklist command', function() { var msg1 = {signal: 'blacklist', args: ['127.0.0.1']}; var msg2 = {signal: 'blacklist', args: ['abc']}; var agent = { notifyAll: function(moduleId, msg) { } }; module.clientHandler(agent, msg1, function(err, result) { result.status.should.eql('ok'); }); module.clientHandler(agent, msg2, function(err, result) { should.exist(err); }); }); it('should execute restart command', function() { var msg1 = {signal: 'restart', ids:['chat-server-1']}; var msg2 = {signal: 'restart', type:'chat', ids:[]}; var agent = { request: function(recordId, moduleId, msg, cb) { cb(null); } }; module.clientHandler(agent, msg1, function(err, result) { should.exist(err); }); module.clientHandler(agent, msg2, function(err, result) { should.exist(err); }); }); }); }); ================================================ FILE: test/pomelo.js ================================================ var pomelo = require('../'); var should = require('should'); var mockBase = process.cwd() + '/test'; describe('pomelo', function() { describe('#createApp', function() { it('should create and get app, be the same instance', function(done) { var app = pomelo.createApp({base: mockBase}); should.exist(app); var app2 = pomelo.app; should.exist(app2); should.strictEqual(app, app2); done(); }); }); }); ================================================ FILE: test/remote/channelRemote.js ================================================ var should = require('should'); var pomelo = require('../../'); var remote = require('../../lib/common/remote/frontend/channelRemote'); var SessionService = require('../../lib/common/service/sessionService'); var ChannelService = require('../../lib/common/service/channelService'); var countDownLatch = require('../../lib/util/countDownLatch'); var MockChannelManager = require('../manager/mockChannelManager'); var mockBase = process.cwd() + '/test'; var WAIT_TIME = 200; describe('channel remote test', function() { describe('#pushMessage', function() { it('should push message the the specified clients', function(done) { var sids = [1, 2, 3, 4, 5, 6]; var uids = [11, 12, 13]; var frontendId = 'frontend-server-id'; var mockRoute = 'mock-route-string'; var mockMsg = {msg: 'some test msg'}; var invokeCount = 0; var invokeUids = []; var sessionService = new SessionService(); sessionService.sendMessageByUid = function(uid, msg) { mockMsg.should.eql(msg); invokeCount++; invokeUids.push(uid); }; var session; for(var i=0, l=sids.length, j=0; i= 0) { cb(null, [uid1]); } else if(rmsg.args[2].indexOf(uid3) >= 0) { cb(null, [uid3]); } else { cb(); } }; var app = pomelo.createApp({base: mockBase}); app.rpcInvoke = mockRpcInvoke; var channelService = new ChannelService(app); channelService.pushMessageByUids(mockMsg, mockUids, function(err, fails) { invokeCount.should.equal(2); should.not.exist(err); should.exist(fails); fails.length.should.equal(2); fails.should.include(uid1); fails.should.include(uid3); done(); }); }); }); describe('#broadcast', function() { it('should push message to all specified frontend servers', function(done) { var mockServers = [ {id: 'connector-1', serverType: 'connector', other: 'xxx1'}, {id: 'connector-2', serverType: 'connector', other: 'xxx2'}, {id: 'area-1', serverType: 'area', other: 'yyy1'}, {id: 'gate-1', serverType: 'gate', other: 'zzz1'}, {id: 'gate-2', serverType: 'gate', other: 'xxx1'}, {id: 'gate-3', serverType: 'gate', other: 'yyy1'} ]; var connectorIds = ['connector-1', 'connector-2']; var mockSType = 'connector'; var mockRoute = 'test.route.string'; var mockBinded = true; var opts = {binded: mockBinded}; var mockMsg = {key: 'some remote message'}; var invokeCount = 0; var sids = []; var mockRpcInvoke = function(sid, rmsg, cb) { invokeCount++; var args = rmsg.args; var route = args[0]; var msg = args[1]; var opts = args[2]; mockMsg.should.eql(msg); mockRoute.should.equal(route); should.exist(opts); mockBinded.should.equal(opts.userOptions.binded); sids.push(sid); cb(); }; var app = pomelo.createApp({base: mockBase}); app.rpcInvoke = mockRpcInvoke; app.addServers(mockServers); var channelService = new ChannelService(app); channelService.broadcast(mockSType, mockRoute, mockMsg, opts, function() { invokeCount.should.equal(2); sids.length.should.equal(connectorIds.length); for(var i=0, l=connectorIds.length; i