Websocket clock server web interface written in ReactJS/TypeScript
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Clock.js 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import * as React from 'react';
  2. import util from './utils';
  3. import Notif from './AlarmNotif';
  4. import Alarms from './Alarms';
  5. import ClockDisp from './ClockDisp';
  6. import './Clock.css';
  7. /* A clock UI for a websocket server handling timezones & alarms */
  8. class Clock extends React.Component {
  9. constructor(props:{ wsurl:string // Websocket URL
  10. }) {
  11. super(props)
  12. // Checking cookies in order to find an existing SESSION
  13. let decoded = '; '+decodeURIComponent(document.cookie)
  14. let cookies = decoded.split('; SESSION=')
  15. let session_id = 'noid'
  16. if(cookies.length == 2) {
  17. session_id = cookies[1].split(';')[0]
  18. }
  19. this.state = {ws: null, // Stores the websocket instance
  20. time: '', // Stores time received from ws
  21. changetz: false, // True when change tz form displayed
  22. tzname: '', // The current tz name
  23. new_tz: '', // tz form input value
  24. alarms: {}, // alarms received from ws
  25. err: '', // error zone content
  26. session_id: session_id, // session id
  27. }
  28. this.handleSubmitTz = this.handleSubmitTz.bind(this)
  29. this.handleChangeTz = this.handleChangeTz.bind(this)
  30. this.alrmUpdate = this.alrmUpdate.bind(this)
  31. this.wsConnect = this.wsConnect.bind(this)
  32. }
  33. render() {
  34. let newtz_lbl = 'Change timezone';
  35. let newtz = <button onClick={this.handleSubmitTz}>{newtz_lbl}</button>;
  36. if(this.state.changetz) {
  37. newtz = (
  38. <form onSubmit={this.handleSubmitTz}>
  39. <input
  40. type="submit"
  41. value={newtz_lbl}
  42. />
  43. <br/>
  44. <label
  45. htmlFor="new-tz"
  46. >
  47. <a
  48. href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List"
  49. target="_blank"
  50. >
  51. Timezone name
  52. </a>&nbsp;
  53. </label>
  54. <input
  55. id="new-tz"
  56. onChange={this.handleChangeTz}
  57. value={this.state.new_tz}
  58. placeholder="Continent/City"
  59. />
  60. </form>
  61. );
  62. }
  63. return (<>
  64. <ClockDisp
  65. time={this.state.time}
  66. tzname={this.state.tzname}
  67. />
  68. <div className="NewTz">
  69. {newtz}
  70. </div>
  71. <Alarms
  72. alarms={this.state.alarms}
  73. ws={this.state.ws}
  74. alrmUpd={this.alrmUpdate}
  75. />
  76. <p>{this.state.err}</p>
  77. </>
  78. );
  79. }
  80. componentDidMount() {
  81. this.wsConnect() // Connect to websocket after clock mount
  82. }
  83. /* Tz edit input form submit handler */
  84. handleSubmitTz(evt) {
  85. evt.preventDefault();
  86. if(!this.state.changetz) {
  87. this.setState({changetz:true})
  88. return;
  89. }
  90. if(this.state.new_tz.length == 0) {
  91. this.setState({changetz:false})
  92. return;
  93. }
  94. this.state.ws.send('tzset '+this.state.new_tz)
  95. this.setState({changetz:false, new_tz:''})
  96. this.alrmUpdate();
  97. return;
  98. }
  99. /* Tz edit input change handler */
  100. handleChangeTz(evt) {
  101. this.setState({new_tz:evt.target.value})
  102. }
  103. /* Send an alarm listing request using the websocket
  104. * Note : actual update will be done onmessage on ws when an
  105. * OK:<JSON> message will be received
  106. */
  107. alrmUpdate() {
  108. this.state.ws.send('alarm list --all')
  109. }
  110. /* Connect/reconnect the websocket */
  111. wsConnect() {
  112. try {
  113. var ws = new WebSocket(this.props.wsurl);
  114. } catch(expt) {
  115. if(expt !== null) {
  116. console.log(expt)
  117. }
  118. return this.wsConnect()
  119. }
  120. /* On connection open send the session ID */
  121. ws.onopen = (evt) => {
  122. this.state.ws.send(this.state.session_id)
  123. this.setState({err:''})
  124. }
  125. /* Incoming messages handler
  126. * Handles the different type of server messages :
  127. * - SESSION:<SESSION_ID> : indicating the session ID to send on next connection
  128. * - OK:[<data>] : on command success with data JSON array on alarm listing
  129. * - ERR:<REASON> : on command error
  130. * - TZN:<TZNAME> : indicating the tz name
  131. * - ALRM:<NAME>: indicating an alarm is ringing
  132. */
  133. ws.onmessage = (message_evt) => {
  134. var msg = message_evt.data
  135. if(msg.startsWith('SESSION:')) {
  136. let session_id = msg.substring(8, msg.length)
  137. let expire = new Date();
  138. expire.setTime(expire.getTime()+(3600*24*1000))
  139. let cookie = 'SESSION='+session_id
  140. cookie += '; expires='+expire.toUTCString()
  141. cookie += '; path=/';
  142. cookie += '; SameSite=strict';
  143. document.cookie = cookie
  144. if(session_id != this.state.session_id) {
  145. console.log('New session')
  146. let tzname = Intl.DateTimeFormat().resolvedOptions().timeZone;
  147. this.state.ws.send('alarm add alrm1')
  148. this.state.ws.send('tzset '+util.escape_arg(tzname))
  149. } else {
  150. console.log('Restoring session '+session_id)
  151. }
  152. this.alrmUpdate()
  153. } else if(msg.startsWith('OK:')) {
  154. msg = msg.substring(3, msg.length)
  155. if(msg.length > 0) {
  156. let alarms = JSON.parse(msg)
  157. this.setState({alarms:alarms})
  158. for(let name in alarms) {
  159. if(alarms[name].ringing) {
  160. Notif.set(name,
  161. alarms[name].time)
  162. } else {
  163. Notif.unset(name)
  164. }
  165. }
  166. }
  167. }
  168. else if(msg.startsWith('ERR:')) {
  169. this.setState({err:msg})
  170. } else if (msg.startsWith('TZN:')) {
  171. msg = msg.substring(4, msg.length)
  172. this.setState({tzname:msg})
  173. } else if (msg.startsWith('ALRM:')) {
  174. msg = msg.substring(5, msg.length)
  175. this.alrmUpdate()
  176. } else {
  177. this.setState({time:msg})
  178. }
  179. }
  180. /* Websocket onclose handler : attempt to reconnect after 2s */
  181. ws.onclose = (evt) => {
  182. this.setState({ err: 'Connection lost, reconnecting...',
  183. ws:null})
  184. setTimeout( () => {
  185. this.wsConnect()
  186. }, 2000)
  187. }
  188. this.setState({ws:ws})
  189. }
  190. }
  191. export default Clock;