import React, { Component } from 'react'; import './Timeline.scss'; import axios from 'axios'; import Event from './Event'; import EventNode from './Components/Timeline/EventNode'; import DateNode from './Components/Timeline/DateNode'; import EndNode from './Components/Timeline/EndNode'; import EventMessages from './Components/Timeline/EventMessages'; import moment from 'moment'; import WebSocketStatus from './Components/WebSocketStatus'; class Timeline extends Component { constructor(props) { super(props); var host = window.location.protocol + '//' + window.location.host; if (process.env.NODE_ENV === 'development') { host = 'https://10.0.0.1:8002'; } this.state = { events: [], loaded: false, error: false, wsIsOpen: false, wsIsAuthenticated: false, wsIsRecovering: false, wsURI: null, wsAuthKey: null, host: host }; this.wsSocket = null; var self = this; setInterval(function() { self.recover(); }, 5000); } componentDidMount() { this.fetchEventList(); this.initUserNotification(); } initUserNotification() { if(!('Notification' in window)) return; // Not yet approved? if (Notification.permission === 'default') { // Request permission return Notification.requestPermission(function() { //console.log("Got permission!"); }); } } showUserNotification(event) { if(!('Notification' in window)) return; if(Notification.permission !== "granted") return; // define new notification var n = new Notification( event.getSubtitle(), { 'body': event.getTitle(), 'tag' : "event-" + event.id } ); // notify when shown successfull n.onshow = function () { console.log("onshow"); }; // remove the notification from Notification Center when clicked. n.onclick = function () { this.close(); console.log("onclick"); }; // callback function when the notification is closed. n.onclose = function () { console.log("onclose"); }; // notification cannot be presented to the user, this event is fired if the permission level is set to denied or default. n.onerror = function () { console.log("onerror"); }; } recover() { var self = this; if(!self.state.wsIsOpen) { self.initWebsocketConnection(self.state.wsURI); return; } if(!self.state.wsIsAuthenticated) { self.authenticateWebsocketConnection(); return; } } fetchEventList() { var self = this; axios.get(this.state.host + '/api/status') .then(function(res) { const events = res.data.events.map(obj => new Event(obj)); const wsURI = res.data['wss-uri'] === undefined ? null : res.data['wss-uri']; self.setState({ events: events, loaded: true, error: false, wsURI: wsURI, wsAuthKey: res.data['auth-key'] }); // Once we get to know the web socket port, we can make the web socket connection if(wsURI && !self.state.wsIsRecovering) { self.initWebsocketConnection(res.data['wss-uri']); } }) .catch(err => { console.warn(err); this.setState({ loaded: true, error: true }); }); } addOrUpdateEvent(event) { this.setState((prevState, props) => { var newEvents = []; var inserted = false; for(var key in prevState.events) { if(prevState.hasOwnProperty(key)) continue; var curEvent = prevState.events[key]; if(curEvent.id === event.id) { newEvents.push(event); inserted = true; } else { newEvents.push(curEvent); } } if(!inserted) { newEvents.push(event); } return { events: newEvents } }); } getEventWithId(id) { var self = this; for(var key in self.state.events) { if(self.state.hasOwnProperty(key)) continue; var event = self.state.events[key]; if(event.id === id) return event; } return undefined; } authenticateWebsocketConnection(authKey) { var self = this; // Authenticate self.wsSocket.send(JSON.stringify({ "type": "authenticate", "auth-key": self.state.wsAuthKey })); } handleJSONMessage(data) { var event; var self = this; // Auth key was invalid, maybe the server restarted if(data.type === "bad-auth-key") { self.setState({ wsIsAuthenticated: false, wsIsRecovering: true }); // Get a fresh auth key self.fetchEventList(); } else if(data.type === "authenticated") { if(self.state.wsIsRecovering) { self.fetchEventList(); } self.setState({ wsIsAuthenticated: true, wsIsRecovering: false }); } else if(data.type === "new-event") { event = new Event(data.event); this.addOrUpdateEvent(event); } else if(data.type === "event-updated") { event = new Event(data.event); this.addOrUpdateEvent(event); } else if(data.type === "event-success") { event = this.getEventWithId(data.id); if(event && event.type === "WebhookAction") { this.showUserNotification(event); } } else { console.log("Unknown event: " + data.type); } } initWebsocketConnection(uri) { var self = this; if(!uri) return; self.wsSocket = new WebSocket(uri); self.wsSocket.binaryType = "arraybuffer"; self.wsSocket.onopen = function() { self.setState({ wsIsOpen: true, }); self.authenticateWebsocketConnection(); }; self.wsSocket.onmessage = (e) => { if (typeof e.data === "string") { try { var data = JSON.parse(e.data); self.handleJSONMessage(data); } catch(e) { console.error(e); } } else { var arr = new Uint8Array(e.data); var hex = ''; for (var i = 0; i < arr.length; i++) { hex += ('00' + arr[i].toString(16)).substr(-2); } console.log("Binary message received: " + hex); } }; self.wsSocket.onclose = function() { self.wsSocket.close(); self.wsSocket = null; self.setState({ wsIsOpen: false, wsIsRecovering: true }); if(self.wsReconnectTimeout !== undefined) { clearTimeout(self.wsReconnectTimeout); } // Try to reconnect again after 2 seconds self.wsReconnectTimeout = setTimeout(function() { self.initWebsocketConnection(uri); self.wsReconnectTimeout = undefined; }, 2000); }; } /* function sendText() { if (isopen) { socket.send("Hello, world!"); console.log("Text message sent."); } else { console.log("Connection not opened.") } }; function sendBinary() { if (isopen) { var buf = new ArrayBuffer(32); var arr = new Uint8Array(buf); for (i = 0; i < arr.length; ++i) arr[i] = i; socket.send(buf); console.log("Binary message sent."); } else { console.log("Connection not opened.") } }; */ getDate(timestamp) { return moment.unix(timestamp).format("YYYY-MM-DD"); } getTimelineObjects() { var rows = []; var last_date = ''; var events = this.state.events; rows.push(); for (var i=events.length-1; i >= 0; i--) { var event = events[i]; rows.push( ); var cur_date = this.getDate(event.timestamp); var next_date = ""; if(i > 0) next_date = this.getDate(events[i-1].timestamp); if(next_date === cur_date) continue; if(cur_date !== last_date) { rows.push(); last_date = cur_date; } } return rows; } render() { if(!this.state.loaded) { return (
Connecting to {this.state.host}..
); } if(this.state.error) { return (
Unable to connect to {this.state.host}
); } return (
{this.getTimelineObjects()}
); } } export default Timeline;