├── LICENSE ├── README.md ├── bin └── ddp-proxy ├── lib └── proxy.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Arunoda Susiripala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Meteor DDP Analyzer 2 | 3 | ### Very Simple DDP Proxy which logs DDP messages 4 | 5 | #### Read more on: [Discover Meteor DDP in Realtime](http://meteorhacks.com/discover-meteor-ddp-in-realtime.html) 6 | [![Discover Meteor DDP in Realtime](https://i.cloudup.com/IsUVXUOspa.png)](http://meteorhacks.com/discover-meteor-ddp-in-realtime.html) 7 | 8 | 9 | ## Installation 10 | 11 | npm install -g ddp-analyzer 12 | 13 | ## Start DDP Analyzer Proxy 14 | 15 | ddp-analyzer-proxy 16 | 17 | ## Start Meteor App 18 | 19 | You need to either start your Meteor App with few configurations 20 | 21 | export DDP_DEFAULT_CONNECTION_URL=http://localhost:3030 22 | meteor 23 | 24 | or just open `http://localhost:3030` from browser. 25 | 26 | Now, open your app in the browser and you'll see DDP logs dumped by `ddp-analyzer-proxy` 27 | 28 | ## Known Issues 29 | 30 | DDP Analyzer currently does not work with WebSockets but instead uses Meteors HTTP based fallback while active. Pull requests adding proper WebSocket support are welcome! 31 | -------------------------------------------------------------------------------- /bin/ddp-proxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var commander = require('commander'); 3 | var proxy = require('../lib/proxy'); 4 | 5 | var optionsParser = commander 6 | .version(require('../package.json').version) 7 | .option('-m, --meteorPort ', 'Meteor App Port [default: 3000]', 3000) 8 | .option('-p, --proxyPort ', 'DDP Proxy Port [default: 3030]', 3030) 9 | .option('-v, --version', 'print version', false); 10 | 11 | var argv = optionsParser.parse(process.argv); 12 | proxy(argv.meteorPort, argv.proxyPort); 13 | 14 | -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var sockjs = require('sockjs'); 3 | var sjsc = require('sockjs-client'); 4 | var tty = require('tty'); 5 | var util = require('util'); 6 | var _ = require('underscore'); 7 | var request = require('request'); 8 | var url = require('url'); 9 | require('colors'); 10 | 11 | var lastTimestamp = {}; 12 | var clientIds = 0; 13 | 14 | module.exports = function(meteorPort, proxyPort) { 15 | var sockjsServer = sockjs.createServer({ 16 | prefix: '/sockjs', 17 | log: function() {}, 18 | // we are proxying /sockjs/info to the actual app 19 | // hence we are using the middleware() of sockjs 20 | // then it can't work with WebSockets 21 | websocket: false 22 | }); 23 | 24 | var middleware = sockjsServer.middleware(); 25 | var server = http.createServer(handleBrowserRequests(middleware, meteorPort)); 26 | sockjsServer.on('connection', onBrowserConnection(meteorPort)); 27 | 28 | server.listen(proxyPort); 29 | printWelcomeMessage(proxyPort); 30 | }; 31 | 32 | function printDdp(where, ddp, clientId) { 33 | try { 34 | if(!lastTimestamp[clientId]) { 35 | lastTimestamp[clientId] = Date.now(); 36 | } 37 | var now = Date.now(); 38 | var timeDiff = now - lastTimestamp[clientId]; 39 | lastTimestamp[clientId] = now; 40 | 41 | if(ddp.forEach) { 42 | ddp.forEach(function(message) { 43 | printMessage(clientId, timeDiff, message, where); 44 | }); 45 | } else { 46 | printMessage(clientId, timeDiff, ddp, where); 47 | } 48 | } catch(ex) { 49 | console.log('DDP_PARSE_ERROR: ', ex.message, ' || DDP_STRING: '); 50 | } 51 | } 52 | 53 | function printWelcomeMessage(proxyPort) { 54 | console.info(''); 55 | console.info('DDP Proxy Started on port: ' + proxyPort); 56 | console.info('==============================='); 57 | console.info('Export following env. variables and start your meteor app'); 58 | console.info(' export DDP_DEFAULT_CONNECTION_URL=http://localhost:3030'); 59 | console.info(' meteor'); 60 | console.info(''); 61 | } 62 | 63 | function printMessage(clientId, timeDiff, message, where) { 64 | var arrow = (where == 'client')? ' OUT ': ' IN '; 65 | if(process.stdout.isTTY) { 66 | var color = (where == 'client')? 'red': 'green'; 67 | arrow = arrow[color].bold.inverse; 68 | timeDiff = (" " + timeDiff + " ").bold.inverse; 69 | clientId = (" " + clientId + " ").yellow.bold.inverse; 70 | } 71 | 72 | var finalMessage = util.format('%s%s%s %s', clientId, arrow, timeDiff, message); 73 | console.log(finalMessage); 74 | } 75 | 76 | function onBrowserConnection(meteorPort) { 77 | return function(conn) { 78 | var clientId = ++clientIds; 79 | console.log(util.format('New Client: [%d]', clientId) + '\n'); 80 | 81 | var meteor = sjsc.create('http://localhost:' + meteorPort + '/sockjs'); 82 | closeBrowserConnection = _.once(conn.close.bind(conn)); 83 | 84 | conn.on('data', function(message) { 85 | meteor.write(message); 86 | printDdp('client', message, clientId); 87 | }); 88 | conn.on('close', meteor.close); 89 | 90 | meteor.on('data', function(message) { 91 | conn.write(message); 92 | printDdp('server', message, clientId); 93 | }); 94 | 95 | meteor.on('error', function() { 96 | closeBrowserConnection(); 97 | meteor.close(); 98 | }); 99 | 100 | meteor.on('close', closeBrowserConnection); 101 | } 102 | } 103 | 104 | function handleBrowserRequests(sockjsMiddleware, meteorPort) { 105 | return function(req, res) { 106 | var parsedUrl = url.parse(req.url); 107 | if(parsedUrl.pathname == "/sockjs/info") { 108 | // we need to forward info request to the actual app 109 | // otherwise Meteor reconnect logic goes crazy 110 | var meteorAppInfoUrl = "http://localhost:" + meteorPort + req.url; 111 | request.get("http://localhost:" + meteorPort + req.url, function(err, r, body) { 112 | if(err) { 113 | res.end(); 114 | } else { 115 | res.writeHead(r.statusCode, r.headers); 116 | res.end(body); 117 | } 118 | }); 119 | } else { 120 | sockjsMiddleware(req, res); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ddp-analyzer", 3 | "description": "Very Simple DDP Proxy which logs DDP messages", 4 | "version": "0.3.0", 5 | "dependencies": { 6 | "commander": "2.x", 7 | "sockjs": "0.3.x", 8 | "sockjs-client": "0.1.x", 9 | "colors": "0.6.x", 10 | "underscore": "1.6.x", 11 | "request": "2.x" 12 | }, 13 | "bin": { 14 | "ddp-analyzer-proxy": "./bin/ddp-proxy" 15 | }, 16 | "author": "Arunoda Susiripala ", 17 | "contributors": [ 18 | { 19 | "name": "Egor Suvorov", 20 | "url": "https://github.com/yeputons/" 21 | } 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/arunoda/meteor-ddp-analyzer.git" 26 | } 27 | } 28 | --------------------------------------------------------------------------------