{"id":151,"date":"2018-05-23T13:14:03","date_gmt":"2018-05-23T13:14:03","guid":{"rendered":"http:\/\/www.bullcrane.com\/angular\/?p=151"},"modified":"2018-05-25T04:11:02","modified_gmt":"2018-05-25T04:11:02","slug":"session-timer-solution","status":"publish","type":"post","link":"http:\/\/www.bullcrane.com\/angular\/2018\/05\/23\/session-timer-solution\/","title":{"rendered":"Session timer solution"},"content":{"rendered":"<p>In my <a href=\"http:\/\/www.bullcrane.com\/angular\/2018\/05\/19\/session-timer-problem\/\">previous blog post<\/a> I described a flaw in my implementation of a session timer.\u00a0 Spotify doesn&#8217;t provide a way to monitor how much time is left but we know that the session lasts for sixty minutes, so I start my own sixty minute timer.\u00a0 My problem was that I was inadvertently creating more than one.<\/p>\n<h6>A singleton timer<\/h6>\n<p>My solution is to create a singleton timer, by subscribing only once.\u00a0 Instead of my class exposing the observable, it exposes the results &#8211; a simple numeric variable for the minutes remaining:<\/p>\n<pre>Observable.timer(0, 60000)\r\n  .map(mins =&gt; 60 - mins)\r\n  .takeWhile(minsRemaining =&gt; minsRemaining &gt;= 0)\r\n  .subscribe(x =&gt; <span style=\"color: #ff0000;\">this.minsRemaining<\/span>=x);<\/pre>\n<p>In the HTML, I rely on Angular to automatically detect changes to minsRemaining.\u00a0 Here is the button disable expression now.\u00a0 The async pipe subscribe is gone:<\/p>\n<pre>&lt;button (click)=\"uploadToSpotify()\" mat-raised-button\r\n  [disabled]=\"!playlistIsEdited()<span style=\"color: #ff0000;\">||minsRemaining&lt;=0<\/span>\"&gt;\r\n  &lt;i class=\"material-icons\"&gt;file_upload&lt;\/i&gt;Upload to Spotify\r\n&lt;\/button&gt;<\/pre>\n<p>I had two other subscribes, not just one more, as I thought in my previous post.\u00a0 I removed them.\u00a0 Now everything references minsRemaining:<\/p>\n<pre>&lt;mat-card style=\"width:120px;text-align:center;\" \r\n  [style.background-color]=\"<span style=\"color: #ff0000;\">minsRemaining &lt;= 5 ? <\/span>'red' : 'transparent'\"&gt;\r\n  &lt;mat-card-content&gt;Session minutes remaining&lt;\/mat-card-content&gt;\r\n  &lt;mat-card-content style=\"font-size:30pt;padding-top:10px;padding-bottom:10px;\"&gt;\r\n    <span style=\"color: #ff0000;\">{{minsRemaining}}<\/span>\r\n  &lt;\/mat-card-content&gt;\r\n&lt;\/mat-card&gt;<\/pre>\n<h6>Survive not only page reloads, but also sleeping<\/h6>\n<p>With the above in place, I started working on how to keep it accurate through page reloads.\u00a0 I got something working, but then found that <em>timer<\/em> stalls when a PC goes to sleep.\u00a0 I&#8217;d wake up a sleeping PC and the timer was off by the number of minutes slept.<\/p>\n<p>So I changed to examining time elapsed on the clock, rather than trying to make my process accurately tick each minute.\u00a0 The following code survives both page reloads and sleeping.<\/p>\n<p>I am refreshing every five seconds now.\u00a0 This keeps my precision to within 5 seconds after page reload or sleeping.\u00a0 The 3600 is 60 minutes in seconds:<\/p>\n<pre>\/\/ sessionStorage survives page reloads\r\n\r\nif (!sessionStorage.getItem(this.accessToken)) {\r\n  sessionStorage\r\n  .setItem(this.accessToken,Date.now().toString());\r\n  }\r\n\r\n\/\/Date.now()-startedAt survives sleeping\r\n\r\nObservable.timer(0,5000)\r\n  .map(() =&gt; parseInt(sessionStorage.getItem(this.accessToken)))\r\n  .map(startedAt =&gt; 3600-(Date.now()-startedAt)\/1000)\r\n  .map(secsRemaining =&gt; Math.ceil(secsRemaining\/60))\r\n  .takeWhile(minsRemaining =&gt; minsRemaining &gt;= 0)\r\n  .subscribe(x =&gt; this.minsRemaining=x);<\/pre>\n<h6>expires_in=3600<\/h6>\n<p>Spotify indicates the number of seconds for the session in the url they return.\u00a0 It&#8217;s always been 3600 seconds, which is 60 minutes, but if they ever do return something different, I should honor it.\u00a0 So instead of hardcoding 3600, I parse it out of the url.\u00a0 This is not some sophisticated thing that I could tap into to find out how much time is left.\u00a0 It&#8217;s simply how many seconds total the token is good for.\u00a0 It stays the same even when the page is refreshed.<\/p>\n<pre>try {<span style=\"color: #ff0000;\">this.expiresIn<\/span>=parseInt(\r\n  window.location.hash.match(\/expires_in=(\\d+)\/)[1]\r\n  );}\r\ncatch (ERR) {this.expiresIn=0}\r\n\r\n\/\/ sessionStorage survives page reloads\r\n\r\nif (!sessionStorage.getItem(this.accessToken)) {\r\n  sessionStorage\r\n  .setItem(this.accessToken,Date.now().toString());\r\n  }\r\n\r\n\/\/Date.now()-startedAt survives sleeping \r\n\r\nObservable.timer(0,5000)\r\n  .map(() =&gt; parseInt(sessionStorage.getItem(this.accessToken)))\r\n  .map(startedAt =&gt; <span style=\"color: #ff0000;\">this.expiresIn<\/span>-(Date.now()-startedAt)\/1000)\r\n  .map(secsRemaining =&gt; Math.ceil(secsRemaining\/60))\r\n  .takeWhile(minsRemaining =&gt; minsRemaining &gt;= 0)\r\n  .subscribe(x =&gt; this.minsRemaining=x);<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my previous blog post I described a flaw in my implementation of a session timer.\u00a0 Spotify doesn&#8217;t provide a way to monitor how much time is left but we know that the session lasts for sixty minutes, so I start my own sixty minute timer.\u00a0 My problem was that I was inadvertently creating more &hellip; <\/p>\n<p class=\"link-more\"><a href=\"http:\/\/www.bullcrane.com\/angular\/2018\/05\/23\/session-timer-solution\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Session timer solution&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7],"tags":[],"_links":{"self":[{"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/posts\/151"}],"collection":[{"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/comments?post=151"}],"version-history":[{"count":17,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/posts\/151\/revisions"}],"predecessor-version":[{"id":171,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/posts\/151\/revisions\/171"}],"wp:attachment":[{"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/media?parent=151"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/categories?post=151"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.bullcrane.com\/angular\/wp-json\/wp\/v2\/tags?post=151"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}