javascripthtmlepubepub3

Local Storage is not shared between pages


I have an epub3 book with 2 pages as well as a Table of Contents Page. I am viewing this book in Apple's Books, their inbuilt epub3 reader, on Mac OSX. The two pages appear side by side. The first page is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=500, height=600"/>
    </head>
    <body>

<p id="result"></p>

<script>
//<![CDATA[
  var current_page = "1";
  var other_page = "2";

  var t = 0;

  setInterval(function() {

    var d = new Date();
    var storage = localStorage; 

    storage.setItem("t"+ current_page, d.toLocaleString());

    document.getElementById("result").innerHTML = storage.getItem("t"+ current_page) +" "+storage.getItem("t"+ other_page);    
  }, 1000);

//]]>
</script>

    </body>
</html>

and the only thing different in my second page is:

  var current_page = "2";
  var other_page = "1";

So every second, Page 1 saves the current time to Local Storage as t1, and Page 2 does the same for the value t2. At the same time, both pages are reading both t1 and t2 from Local Storage, before their values are displayed to screen. However in ibooks, Page 1 only manages to display the current value for t2 when the page is reloaded - like when I flip to the Table of Contents and then back to Page 1 and 2 again. With something similar happening for Page 2 with regard to t1.

So at time 21:10:00, Page 1 might display:

08/09/19, 21:09:18 08/09/19, 21:08:58

and Page 2:

08/09/19, 21:09:22 08/09/19, 21:08:01

I also tried using Session Data but Page 1 can't ever read t2 and Page 2 can't read t1. So, this would be displayed instead:

08/09/19, 21:09:18 null

I can think of several applications where it would be very useful for Pages to communicate with each other.

For example, if a video is playing on one page, it would be useful to stop it if a video on another page is started. This would normally be done using Session Storage. This is related to my own use case and the reason I started exploring this problem.

Likewise, if the user is asked on Page 1 to enters the name of the main character of the story, then that entry should appear immediately on Page 2 once it is entered.

Is there any other way for Pages to communicate with each other in epub3 other than Local or Session Storage?


Solution

  • I dont know epub3 and dont have a MAC to test, but here are four possible solutions that come to my mind:

    Cookies

    It is not as performant as localStorage for that use-case, but if you dont have many options, better that than nothing.

    Functions to create, read and delete cookies (Credits to https://stackoverflow.com/a/28230846/9150652):

    function setCookie(name,value,days) {
        var expires = "";
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days*24*60*60*1000));
            expires = "; expires=" + date.toUTCString();
        }
        document.cookie = name + "=" + (value || "")  + expires + "; path=/";
    }
    function getCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) {
            var c = ca[i];
            while (c.charAt(0)==' ') c = c.substring(1,c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
        }
        return null;
    }
    function eraseCookie(name) {   
        document.cookie = name+'=; Max-Age=-99999999;';  
    }
    
    

    Usage for your example:

    <script>
    //<![CDATA[
      var current_page = "1";
      var other_page = "2";
    
      var t = 0;
    
      setInterval(function() {
    
        var d = new Date();
    
        setCookie("t"+ current_page, d.toLocaleString(), 100); // 100 days
    
        document.getElementById("result").innerHTML = getCookie("t"+ current_page) +" "+getCookie("t"+ other_page);    
      }, 1000);
    
    //]]>
    </script>
    

    BroadcastChannel

    BroadcastChannel is a very new functionality, so it might not be supported by the "Books" app. But here is a concept:

    <script>
    //<![CDATA[
      var broadcaster = new BroadcastChannel('test');
      var current_page = "1";
      var other_page = "2";
    
      var t = 0;
    
      setInterval(function() {
    
        var d = new Date();
    
        // Send message to all other tabs with BroadcastChannel('test')
        bc.postMessage({
            senderPage: "t"+ current_page,
            date: d.toLocaleString()
        });
      }, 1000);
    
      broadcaster.onmessage = (result) => {
          if(result.senderPage == "t"+ other_page) { // If the message is from the other page
            // Set HTML to current date + sent Date from other page
            var d = new Date();
            document.getElementById("result").innerHTML = d.toLocaleString() +" "+result.date;    
          }
      };
    
    //]]>
    </script>
    

    Some sort of Backend

    If none of the above works, you probably have no other option, than to use some sort of backend, to provide and save the data

    If it is just for you, I suggest you to use a free tier of Firebase or MongoDB Atlas, as they both provide quite some value on their free tier.

    If you do it with a Backend, it could be done with something like this:

    <script>
    //<![CDATA[
      var current_page = "1";
      var other_page = "2";
      var lastLocalDate = new Date();
      const serverUrl = "http://someUrl.com/endpoint/"
    
      // Gets the latest date of the other page via ajax
      function getUpdate() {
        var xmlhttp = new XMLHttpRequest();
    
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == XMLHttpRequest.DONE) {
                // If successful, update HTML
                if (xmlhttp.status == 200) {
                    document.getElementById("result").innerHTML = lastLocalDate.toLocaleString() +" "+xhr.responseText;
                }
    
                // Update the date of this page anyways
                sendUpdate();
            }
        };
    
        // GET request with parameter requestingPage, which is the other page
        xmlhttp.open("GET", serverUrl, true);
        xmlhttp.send(`requestingPage=${other_page}`);
      }
    
      // Sends the current date of this page to the webserver
      function sendUpdate() {
        var xmlhttp = new XMLHttpRequest();
    
        // No need to check if successful, just update the page again
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == XMLHttpRequest.DONE) {
                getUpdate();
            }
        };
    
        lastLocalDate = new Date();
    
        // POST request with parameters page and date
        xmlhttp.open("POST", serverUrl, true);
        xmlhttp.send(`sendingPage=${current_page}&data=${lastLocalDate.toLocaleString()}`);
      }
    
      // Start with sending an update (so that lastLocalDate is at least sent once to the server)
      sendUpdate();
    //]]>
    </script>
    

    And some methods in your backend that need to look something like this (note that this is not valid code in any language):

    @GET
    function getDate(requestingPageId)
        find latest entry with page.id == requestingPageId
        return page.date
    
    @POST
    function saveDate(savingPage, savingDate)
        store new page element with 
            page.id = savingPage
            page.date = savingDate
    

    And a collection in your database looking like this:

    [
        {
            id: 1,
            date: "date"
        },{
            id: 2,
            date: "date"
        },{
            id: 2,
            date: "date"
        },{
            id: 1,
            date: "date"
        },
    
        // ...
    ]
    

    Window References

    If the Books app opens the second tab from the first tab, it might be worth to look into: