├── .gitignore ├── index.js ├── .DS_Store ├── lib ├── .DS_Store └── pomelo-client.js ├── index.d.ts ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | lib/.DS_Store -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./lib/pomelo-client"); -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessu/pomelo-node-client-websocket/HEAD/.DS_Store -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lessu/pomelo-node-client-websocket/HEAD/lib/.DS_Store -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import {EventEmitter} from "events"; 2 | declare interface PomeloClientParams{ 3 | host : string; 4 | port : number; 5 | log ?: boolean; 6 | user ?: any; 7 | } 8 | declare interface PomeloClient extends EventEmitter{ 9 | init(params:PomeloClientParams, cb:()=>void) : void; 10 | request(route:string,msg:any,cb:(response:any)=>void); 11 | disconnect():void; 12 | notify(route:string, msg:any); 13 | 14 | } 15 | 16 | declare let create : () => PomeloClient; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomelo-node-client-websocket", 3 | "version": "0.1.0", 4 | "description": "pomelo client for nodejs using websocket", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/lessu/pomelo-node-client-websocket" 9 | }, 10 | "keywords": [ 11 | "pomelo", 12 | "client", 13 | "websocket" 14 | ], 15 | "author": "lessu", 16 | "license": "MIT", 17 | "dependencies": { 18 | "ws": "^0.4.25" 19 | , "pomelo-protobuf": "^0.3.5" 20 | , "pomelo-protocol": "^0.1.0" 21 | , "@types/node" : "^4.0.30" 22 | } 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pomelo javascript websocket client (Nodejs/Multi Client) 2 | 3 | The javascript websocket client library for [Pomelo](https://github.com/NetEase/pomelo). 4 | 5 | Since there are two kind connectors in pomelo 0.3, socket.io and socket(websocket), we provide two javascript clients for different usage. 6 | [websocket client](https://github.com/pomelonode/pomelo-jsclient-websocket) is optimized for data transfer size, the package is compressedin high rate. It's suitable for HTML5 online game, especially mobile platform. 7 | 8 | [socket.io client](https://github.com/pomelonode/pomelo-jsclient-socket.io) is excellent for browser compatibility, the package is in json. It's suitable for online realtime application on browser, like chat, on which browser compatiblity is an important issue. 9 | 10 | The apis are almost the same in both clients, except websocket client need a handshake callback for protocol data. 11 | Both clients use [component](https://github.com/component/component/) package manager for building. 12 | 13 | *** 14 | 15 | ## Notice 16 | 17 | This fork Works in nodejs. Can't work in a broswer. 18 | 19 | ## Different to origin 20 | - API 21 | 22 | It's hard to write testcases if pomelo-client can only have one client available in a project,especially in an IM / chat project. 23 | 24 | So a new function named **'create'** is added, 25 | ``` javascript 26 | var pomelo = pomelo_client.create(); 27 | ``` 28 | 29 | - Typescript definition 30 | 31 | Add index.d.ts for typescript. 32 | 33 | *** 34 | 35 | ## Usage 36 | ### install 37 | ```bash 38 | npm install pomelo-node-client-websocket 39 | ``` 40 | ### create a client 41 | ``` javascript 42 | var pomelo_client = require('pomelo-jsclient-websocket-node'); 43 | var pomelo = pomelo_client.create(); 44 | ``` 45 | ### connect to the server 46 | ``` javascript 47 | pomelo.init(params, callback); 48 | ``` 49 | params object are 50 | 51 | example: 52 | ``` javascript 53 | pomelo.init({ 54 | host: host, 55 | port: port, 56 | user: {}, 57 | handshakeCallback : function(){} 58 | }, function() { 59 | console.log('success'); 60 | }); 61 | ``` 62 | 63 | user field is user define json content 64 | handshakeCallback field is handshake callback function 65 | 66 | ### send request to server with callback 67 | ``` javascript 68 | pomelo.request(route, msg, callback); 69 | ``` 70 | 71 | example: 72 | ``` javascript 73 | pomelo.request(route, { 74 | rid: rid 75 | }, function(data) { 76 | console.log(dta); 77 | }); 78 | ``` 79 | 80 | ### send request to server without callback 81 | ``` javascript 82 | pomelo.notify(route, params); 83 | ``` 84 | 85 | ### receive message from server 86 | ``` javascript 87 | pomelo.on(route, callback); 88 | ``` 89 | 90 | example: 91 | ``` javascript 92 | pomelo.on('onChat', function(data) { 93 | addMessage(data.from, data.target, data.msg); 94 | $("#chatHistory").show(); 95 | }); 96 | ``` 97 | 98 | ### disconnect from server 99 | ``` javascript 100 | pomelo.disconnect(); 101 | ``` 102 | 103 | ## License 104 | (The MIT License) 105 | 106 | Copyright (c) 2012-2015 NetEase, Inc. and other contributors 107 | 108 | 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: 109 | 110 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 111 | 112 | 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. 113 | -------------------------------------------------------------------------------- /lib/pomelo-client.js: -------------------------------------------------------------------------------- 1 | var WebSocket = require('ws'); 2 | var Protocol = require('pomelo-protocol'); 3 | var Package = Protocol.Package; 4 | var Message = Protocol.Message; 5 | var EventEmitter = require('events').EventEmitter; 6 | var protocol = require('pomelo-protocol'); 7 | var protobuf = require('pomelo-protobuf'); 8 | 9 | if (typeof Object.create !== 'function') { 10 | Object.create = function (o) { 11 | function F() {} 12 | F.prototype = o; 13 | return new F(); 14 | }; 15 | } 16 | 17 | var JS_WS_CLIENT_TYPE = 'js-websocket'; 18 | var JS_WS_CLIENT_VERSION = '0.0.1'; 19 | 20 | var RES_OK = 200; 21 | var RES_OLD_CLIENT = 501; 22 | function create(){ 23 | var pomelo = Object.create(EventEmitter.prototype); // object extend from object 24 | var socket = null; 25 | var reqId = 0; 26 | var callbacks = {}; 27 | var handlers = {}; 28 | var routeMap = {}; 29 | 30 | var heartbeatInterval = 5000; 31 | var heartbeatTimeout = heartbeatInterval * 2; 32 | var nextHeartbeatTimeout = 0; 33 | var gapThreshold = 100; // heartbeat gap threshold 34 | var heartbeatId = null; 35 | var heartbeatTimeoutId = null; 36 | 37 | var handshakeCallback = null; 38 | 39 | var handshakeBuffer = { 40 | 'sys':{ 41 | type: JS_WS_CLIENT_TYPE, 42 | version: JS_WS_CLIENT_VERSION 43 | }, 44 | 'user':{ 45 | } 46 | }; 47 | 48 | var initCallback = null; 49 | 50 | pomelo.init = function(params, cb){ 51 | pomelo.params = params; 52 | params.debug = true; 53 | initCallback = cb; 54 | var host = params.host; 55 | var port = params.port; 56 | 57 | var url = 'ws://' + host; 58 | if(port) { 59 | url += ':' + port; 60 | } 61 | 62 | if (!params.type) { 63 | console.log('init websocket'); 64 | handshakeBuffer.user = params.user; 65 | handshakeCallback = params.handshakeCallback; 66 | this.initWebSocket(url,cb); 67 | } 68 | }; 69 | 70 | pomelo.initWebSocket = function(url,cb){ 71 | console.log(url); 72 | var onopen = function(event){ 73 | console.log('[pomeloclient.init] websocket connected!'); 74 | var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer))); 75 | send(obj); 76 | }; 77 | var onmessage = function(event) { 78 | processPackage(Package.decode(event.data), cb); 79 | // new package arrived, update the heartbeat timeout 80 | if(heartbeatTimeout) { 81 | nextHeartbeatTimeout = Date.now() + heartbeatTimeout; 82 | } 83 | }; 84 | var onerror = function(event) { 85 | pomelo.emit('io-error', event); 86 | console.log('socket error %j ',event); 87 | }; 88 | var onclose = function(event){ 89 | pomelo.emit('close',event); 90 | console.log('socket close %j ',event); 91 | }; 92 | socket = new WebSocket(url); 93 | socket.binaryType = 'arraybuffer'; 94 | socket.onopen = onopen; 95 | socket.onmessage = onmessage; 96 | socket.onerror = onerror; 97 | socket.onclose = onclose; 98 | }; 99 | 100 | pomelo.disconnect = function() { 101 | if(socket) { 102 | if(socket.disconnect) socket.disconnect(); 103 | if(socket.close) socket.close(); 104 | console.log('disconnect'); 105 | socket = null; 106 | } 107 | 108 | if(heartbeatId) { 109 | clearTimeout(heartbeatId); 110 | heartbeatId = null; 111 | } 112 | if(heartbeatTimeoutId) { 113 | clearTimeout(heartbeatTimeoutId); 114 | heartbeatTimeoutId = null; 115 | } 116 | }; 117 | 118 | pomelo.request = function(route, msg, cb) { 119 | msg = msg || {}; 120 | route = route || msg.route; 121 | if(!route) { 122 | console.log('fail to send request without route.'); 123 | return; 124 | } 125 | 126 | reqId++; 127 | sendMessage(reqId, route, msg); 128 | 129 | callbacks[reqId] = cb; 130 | routeMap[reqId] = route; 131 | }; 132 | 133 | pomelo.notify = function(route, msg) { 134 | msg = msg || {}; 135 | sendMessage(0, route, msg); 136 | }; 137 | 138 | var sendMessage = function(reqId, route, msg) { 139 | var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; 140 | 141 | //compress message by protobuf 142 | var protos = !!pomelo.data.protos?pomelo.data.protos.client:{}; 143 | if(!!protos[route]){ 144 | msg = protobuf.encode(route, msg); 145 | }else{ 146 | msg = Protocol.strencode(JSON.stringify(msg)); 147 | } 148 | 149 | var compressRoute = 0; 150 | if(pomelo.dict && pomelo.dict[route]){ 151 | route = pomelo.dict[route]; 152 | compressRoute = 1; 153 | } 154 | 155 | msg = Message.encode(reqId, type, compressRoute, route, msg); 156 | var packet = Package.encode(Package.TYPE_DATA, msg); 157 | send(packet); 158 | }; 159 | 160 | 161 | var _host = ""; 162 | var _port = ""; 163 | var _token = ""; 164 | 165 | /* 166 | var send = function(packet){ 167 | if (!!socket) { 168 | socket.send(packet.buffer || packet,{binary: true, mask: true}); 169 | } else { 170 | setTimeout(function() { 171 | entry(_host, _port, _token, function() {console.log('Socket is null. ReEntry!')}); 172 | }, 3000); 173 | } 174 | }; 175 | */ 176 | 177 | var send = function(packet){ 178 | if (!!socket) { 179 | //8192!? problem fix packet.buffer || 180 | socket.send(packet, {binary: true, mask: true}); 181 | } 182 | }; 183 | 184 | 185 | var handler = {}; 186 | 187 | var heartbeat = function(data) { 188 | var obj = Package.encode(Package.TYPE_HEARTBEAT); 189 | if(heartbeatTimeoutId) { 190 | clearTimeout(heartbeatTimeoutId); 191 | heartbeatTimeoutId = null; 192 | } 193 | 194 | if(heartbeatId) { 195 | // already in a heartbeat interval 196 | return; 197 | } 198 | 199 | heartbeatId = setTimeout(function() { 200 | heartbeatId = null; 201 | send(obj); 202 | 203 | nextHeartbeatTimeout = Date.now() + heartbeatTimeout; 204 | heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout); 205 | }, heartbeatInterval); 206 | }; 207 | 208 | var heartbeatTimeoutCb = function() { 209 | var gap = nextHeartbeatTimeout - Date.now(); 210 | if(gap > gapThreshold) { 211 | heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap); 212 | } else { 213 | console.error('server heartbeat timeout'); 214 | pomelo.emit('heartbeat timeout'); 215 | pomelo.disconnect(); 216 | } 217 | }; 218 | 219 | var handshake = function(data){ 220 | data = JSON.parse(Protocol.strdecode(data)); 221 | if(data.code === RES_OLD_CLIENT) { 222 | pomelo.emit('error', 'client version not fullfill'); 223 | return; 224 | } 225 | 226 | if(data.code !== RES_OK) { 227 | pomelo.emit('error', 'handshake fail'); 228 | return; 229 | } 230 | 231 | handshakeInit(data); 232 | 233 | var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK); 234 | send(obj); 235 | if(initCallback) { 236 | initCallback(socket); 237 | initCallback = null; 238 | } 239 | }; 240 | 241 | var onData = function(data){ 242 | //probuff decode 243 | var msg = Message.decode(data); 244 | 245 | if(msg.id > 0){ 246 | msg.route = routeMap[msg.id]; 247 | delete routeMap[msg.id]; 248 | if(!msg.route){ 249 | return; 250 | } 251 | } 252 | 253 | msg.body = deCompose(msg); 254 | 255 | processMessage(pomelo, msg); 256 | }; 257 | 258 | var onKick = function(data) { 259 | pomelo.emit('onKick'); 260 | }; 261 | 262 | handlers[Package.TYPE_HANDSHAKE] = handshake; 263 | handlers[Package.TYPE_HEARTBEAT] = heartbeat; 264 | handlers[Package.TYPE_DATA] = onData; 265 | handlers[Package.TYPE_KICK] = onKick; 266 | 267 | var processPackage = function(msg){ 268 | handlers[msg.type](msg.body); 269 | }; 270 | 271 | var processMessage = function(pomelo, msg) { 272 | if(!msg || !msg.id) { 273 | // server push message 274 | // console.error('processMessage error!!!'); 275 | pomelo.emit(msg.route, msg.body); 276 | return; 277 | } 278 | 279 | //if have a id then find the callback function with the request 280 | var cb = callbacks[msg.id]; 281 | 282 | delete callbacks[msg.id]; 283 | if(typeof cb !== 'function') { 284 | return; 285 | } 286 | 287 | cb(msg.body); 288 | return; 289 | }; 290 | 291 | var processMessageBatch = function(pomelo, msgs) { 292 | for(var i=0, l=msgs.length; i