├── LICENSE.md ├── influxdb.min.js ├── influxdb.js └── README.md /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 boynet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /influxdb.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function _classCallCheck(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}var _createClass=function(){function a(a,b){for(var c=0;c1&&(d+=","),d+=e+"="+f);return d}},{key:"isEmpty",value:function(b){for(var c in b)if(b.hasOwnProperty(c))return!1;return!window.JSON||JSON.stringify(b)===JSON.stringify({})}}]),a}(); 2 | -------------------------------------------------------------------------------- /influxdb.js: -------------------------------------------------------------------------------- 1 | class Influxdb { 2 | constructor(host, sendPointsOnClose) { 3 | this.sendPointsOnClose = sendPointsOnClose; 4 | this.host = host; 5 | this.points = []; 6 | this.beaconSent = false; 7 | if (this.sendPointsOnClose) this.registerUnloadEvent(); 8 | return this; 9 | } 10 | 11 | registerUnloadEvent() { 12 | var _self = this; 13 | //need both events to work in chrome http://stackoverflow.com/a/20322988/1368683 14 | window.addEventListener('unload', function (e) { 15 | _self.sendBeacon(_self); 16 | }, false); 17 | window.onbeforeunload = function (e) { 18 | _self.sendBeacon(_self); 19 | }; 20 | } 21 | 22 | sendBeacon(_self) { 23 | //need this polyfill https://github.com/miguelmota/Navigator.sendBeacon 24 | if (_self.beaconSent) return; 25 | if (_self.points && _self.points.length == 0) return; 26 | _self.beaconSent = true; 27 | if (!navigator && !navigator.sendBeacon) return; 28 | var data = _self.implodePoints(); 29 | navigator.sendBeacon(_self.host, data); 30 | } 31 | 32 | point(key, fields, tags, time) { 33 | return this._addPoint(new Influxdb_Point(key, fields, tags, time)); 34 | } 35 | 36 | _addPoint(point) { 37 | if (point.isValid()) { 38 | this.points.push(point); 39 | } 40 | return this; 41 | } 42 | 43 | implodePoints() { 44 | if (this.points.length == 0) return ''; 45 | var index, data = ''; 46 | for (index = 0; index < this.points.length; ++index) { 47 | if (!this.points[index].isValid()) { 48 | this.points.slice(index, 1); 49 | continue; 50 | } 51 | data = data + this.points[index].getLine(); 52 | } 53 | return data; 54 | } 55 | 56 | send() { 57 | if (this.points.length == 0) return false; 58 | var data = this.implodePoints(); 59 | if (data) { 60 | var request = new XMLHttpRequest(); 61 | request.open('POST', this.host, true); 62 | request.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8'); 63 | request.send(data); 64 | } 65 | 66 | this.points = []; 67 | } 68 | 69 | 70 | } 71 | 72 | class Influxdb_Point { 73 | 74 | constructor(key, fields, tags, time) { 75 | this.key = key; 76 | if (typeof fields !== 'undefined' && !this.isEmpty(fields)) this.fields = fields; 77 | if (typeof tags !== 'undefined' && !this.isEmpty(fields)) this.tags = tags; 78 | if (typeof time !== 'undefined') this.time = time; 79 | 80 | } 81 | 82 | isValid() { 83 | if (!this.key || !this.fields) return false; 84 | return true; 85 | } 86 | 87 | getLine() { 88 | if (!this.key && !this.fields) return; 89 | this.line = this.key; 90 | if (this.tags) { 91 | this.line = this.line + ',' + this.objToString(this.tags) 92 | } 93 | if (this.fields) { 94 | this.line = this.line + ' ' + this.objToString(this.fields); 95 | } 96 | if (this.time) { 97 | this.line = this.line + ' ' + this.time; 98 | } 99 | this.line = this.line + '\n'; 100 | return this.line; 101 | } 102 | 103 | sortObjectByKey(obj) { 104 | if (!Array.prototype.forEach) return obj; 105 | if (!Array.prototype.sort) return obj; 106 | if (!Object.keys) return obj; 107 | var ordered = {}; 108 | Object.keys(obj).sort().forEach(function (key) { 109 | ordered[key] = obj[key]; 110 | }); 111 | return ordered; 112 | } 113 | 114 | objToString(obj) { 115 | var i = 0; 116 | var str = ''; 117 | var key, value; 118 | //Tags should be sorted by key before being sent for best performance https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/#key 119 | obj = this.sortObjectByKey(obj); 120 | for (var key in obj) { 121 | if (obj.hasOwnProperty(key)) { 122 | //todo: right now not supporting space in tags where it should be support with back slash 123 | key = key.replace(/\s+/g, ''); 124 | value = obj[key].toString(); 125 | value = value.replace(/\s+/g, ''); 126 | i++; 127 | if (i > 1) str = str + ','; 128 | str += key + '=' + value; 129 | } 130 | } 131 | return str; 132 | } 133 | 134 | isEmpty(obj) { 135 | for (var prop in obj) { 136 | if (obj.hasOwnProperty(prop)) 137 | return false; 138 | } 139 | if (!window.JSON) return true; 140 | return true && JSON.stringify(obj) === JSON.stringify({}); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Influxdb js client 2 | Lightweight library for sending metrics to influxdb in javascirpt 3 | 4 | ---------- 5 | 6 | ## Why another libray? 7 | 8 | Others influxdb js libaries are intented to use with node.js, so they are big in size and just to complicated for this simple job. 9 | 10 | this libary just taking simple data and transform it to [influxdb line protocol](https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/) 11 | 12 | ## What's the difference between this libary and sending simple xhr? 13 | 14 | 1. this library transform plain objects into valid line protocol 15 | 2. give you easy way to send points in batch 16 | 3. "lose as less points as possible" by using navigator.sendBeacon when the user leave the page and there is some unsend data(OFF by default) 17 | 4. only **2 KB minified** size 18 | 19 | # How to use? 20 | 21 | include influxdb.min.js in your page: 22 | 23 | *the unminifed version is in es6 if you want to use the unminifed version than you need to transform the code to es5 using [babel](https://babeljs.io/repl/) 24 | ```html 25 | 26 | ``` 27 | 28 | connect to the server: 29 | 30 | (its not sending anything just constructing the class) 31 | ```javascript 32 | influxdb = new Influxdb('http://127.0.0.1:8086/write?db=DBNAME',true); 33 | ``` 34 | replace 127.0.0.1 with the ip/url of your influxdb server, change DBNAME with the name of the db you want send points to; 35 | 36 | insert points: 37 | ```javascript 38 | influxdb.point('key',{value:1},{tag:'tag_name'}); 39 | influxdb.point('key',{value:2,other_value:3},{tag:'tag_name',othertag:'some_value'}); 40 | ``` 41 | send the points: 42 | 43 | ```javascript 44 | influxdb.send(); 45 | ``` 46 | 47 | 48 | you can also use the short verse like: 49 | 50 | ```javascript 51 | influxdb.point('key',{value:1},{tag:'tag_name'}).send(); 52 | ``` 53 | 54 | # Api 55 | 56 | ```javascript 57 | Influxdb.constructor(host, sendPointsOnClose) 58 | ``` 59 | 60 | - host - should get url to send the points to like: `http://127.0.0.1:8086/write?db=DBNAME` 61 | - if your db has auth enabled(which should be) apped the username and password according to the docs like `http://127.0.0.1:8086/write?db=DBNAME&u=username&p=password` 62 | - if you sending custom time with your point append the precision like: 63 | `http://127.0.0.1:8086/write?db=DBNAME&u=username&p=password&precision=ms` possible values are `precision=[n,u,ms,s,m,h]` for nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively. if you use Date.now() as time than use `&precision=ms` 64 | - for full list option please see the docs for [http write synax](https://docs.influxdata.com/influxdb/v0.13/write_protocols/write_syntax/#http) 65 | - sendPointsOnClose (default: false) - if set to true than if for some reason there is a points that added to the batch but was no sent yet to the server and the user close the tab\browser it will send the points using the new api `navigator.sendBeacon` if you want to use it in old browser please include the polyfill https://github.com/miguelmota/Navigator.sendBeacon 66 | 67 | 68 | 69 | ````.point(key, fields, tags, time)```` 70 | 71 | each point must have at least key and one fields look here for more info: [influxdb line protocol](https://docs.influxdata.com/influxdb/v0.13/write_protocols/line/) 72 | 73 | - key - `string` the measurement name 74 | - fields - `object` { alert=true,reason="value above maximum threshold"2} 75 | - tags - `null|object` { url : "/index", user_id : 1234 } 76 | - time - `null|string|number` the time in which the data happend (if you use custom time than dont forget to add the precision to influxdb constructor, Date.now() = ms precision) 77 | 78 | # Security 79 | always use this libary with [Authentication and Authorization](https://docs.influxdata.com/influxdb/v0.13/administration/authentication_and_authorization/). 80 | 81 | + create a new database for public data for example named `website_public`. 82 | + create a new user with only `WRITE` privilege for the `website_public` DB. 83 | + connect to the influxdb server and append the above created user and password to the host url like: 84 | `http://127.0.0.1:8086/write?db=website_public&u=username&p=password` 85 | 86 | **pay attention!** always monitor your influxdb server for memory usage as this libary allow anyone to flood your server with unwanted tags [according to the docs](https://docs.influxdata.com/influxdb/v0.13/guides/hardware_sizing/) low hardware server can handle only 100,000 tags. 87 | never blindly trust the data you get 88 | 89 | 90 | # Example 91 | Gather some statics about the page loading time and sending it to influxdb with same timestamp: 92 | ```javascript 93 | influxdb = new Influxdb('http://127.0.0.1:8086/write?db=website&u=website_public&p=12341234&precision=ms',true); 94 | 95 | var url = encodeURIComponent(window.location.pathname+window.location.search); 96 | var time = Date.now(); 97 | 98 | influxdb.point("pageview", {value: 1}, {url: url}, time); 99 | 100 | $(window).load(function () { 101 | if (typeof window.performance != "undefined") { 102 | var page_latency = window.performance.timing.responseStart - window.performance.timing.connectStart; 103 | var load_time = window.performance.timing.loadEventStart - window.performance.timing.navigationStart; 104 | 105 | influxdb.point("page_latency", {value: page_latency}, {url: url}, time); 106 | influxdb.point("load_time", {value: load_time}, {url: url}, time); 107 | 108 | influxdb.send(); 109 | } 110 | }); 111 | ``` 112 | 113 | Although we didn't send the 'pageview' point only after window.load is fired, if for some reason the user exit the website before the event is fired the point will sent anyway due to our usage of `sendPointsOnClose` (second argument on the constructer) 114 | 115 | # TODO list: 116 | - [ ] supporting esacped space in key name 117 | - [ ] supporting for resend faild xhr reuquest 118 | --------------------------------------------------------------------------------