ffrgb / meshviewer

Meshviewer - more in the README
https://regensburg.freifunk.net/netz/karte/
GNU Affero General Public License v3.0
50 stars 58 forks source link

Better embedding support #316

Open micw opened 2 years ago

micw commented 2 years ago

Is your feature request related to a problem? Please describe.

Currently, when embedding the map in an iframe, deep linking is problematic. The Demo at https://regensburg.freifunk.net/netz/karte/ has a partial solution which could easily improved. Meshviewer handles deep links via URL anchors (like #!/de/map/a42bb0c909aa). The Demo hosts meshviewer on the same domain as the embedding page and polls the iframe for URL changes. This allows to change the main page URL when the embedded URL is changed. This will not work, if meshviewer runs on a separate domain. Also there's no handling for deep links, so an URL like https://regensburg.freifunk.net/netz/karte/node/14cc2097d7d6/ will always show the full map with no node selected.

Describe the solution you'd like

Modify mapviewer to advertise url anchor changes via window.postMessage (https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). This allows to cunsume these events on the embedding window without polling and works also on cross-domain setups.

Provide a javascript as part of meshviewer that handles and embedded meshviewer:

With this, embedding a meshviewer with working deep-linking would be as easy as

<iframe id="meshviewer-embedded" src="https://meshviewer.freifunk-sonstwo.org/"/>
<script src="https://meshviewer.freifunk-sonstwo.org/embed.js"/>
micw commented 2 years ago

Change to meshviewer itself is minimal:

In main.js, before the router is created, I just added:

      window.onhashchange = function () {
        window.postMessage({hash: window.location.hash}, '*');
      };

This changes in the hash so that the embedding window can access it.

The embed.js I created looks like this:

(function() {
    var iframe=document.getElementById("meshviewer-embedded")
    if (!iframe) {
        console.log("IFrame 'meshviewer-embedded' not found")
        return;
    }
    if (!iframe.contentWindow) {
        console.log("Element 'meshviewer-embedded' seems not to be a valid iframe")
        return;
    }
    if (document.location.hash) {
        iframe.contentWindow.location.hash = document.location.hash;
    }
    iframe.contentWindow.addEventListener("message", (event) => {
        if (event && event.data && event.data.hash) {
            window.location.hash = event.data.hash;
        }
    }, false);
    window.onhashchange = function () {
        iframe.contentWindow.location.hash = document.location.hash;
    };
}) ();

This works almost completely with one error. It seems that meshviewer has a race condition between initializing the app and reacting on hash changes. So the line iframe.contentWindow.location.hash = document.location.hash; which adds the hash from the embedding window to the embedded window causes an error like

Uncaught TypeError: Cannot read properties of undefined (reading 'b0be76df915a')
    at app.js?t=1635884604584:42
    at app.js?t=1635884604584:42
    at d (app.js?t=1635884604584:42)
    at app.js?t=1635884604584:42
    at d (app.js?t=1635884604584:42)
    at u.resolve (app.js?t=1635884604584:42)
    at u._onLocationChange (app.js?t=1635884604584:42)

Edit: The race condition can be workarounded by running the initial setting of the hash decoupled via window.setTimeout:

(function() {
    var iframe=document.getElementById("meshviewer-embedded")
    if (!iframe) {
        console.log("IFrame 'meshviewer-embedded' not found")
        return;
    }
    if (!iframe.contentWindow) {
        console.log("Element 'meshviewer-embedded' seems not to be a valid iframe")
        return;
    }
    if (document.location.hash) {
        window.setTimeout(function() {
            iframe.contentWindow.location.hash = document.location.hash;
        }, 0);
    }
    iframe.contentWindow.addEventListener("message", (event) => {
        if (event && event.data && event.data.hash) {
            window.location.hash = event.data.hash;
        }
    }, false);
    window.onhashchange = function () {
        iframe.contentWindow.location.hash = document.location.hash;
    };
}) ();
herbetom commented 2 years ago

What's currently also problematic are those Links in the linkList. On regensburg.freifunk.net/netz/karte/ those are Impressum and Datenschutz. If you click them they will open inside the iframe which doen't really make a whole lot of sense. The easiest solution would probaly be to open them in a new tab/window in generall.

micw commented 2 years ago

@herbetom I think that's a different issue.

I have closed my PR since it's not 100% working. My (working) changes are in https://git.dezentrale.cloud/Freifunk-Leipzig/meshviewer/src/branch/ffle now.

The structure of meshviewer makes it hard to do proper PRs since the repo mixes config and development. When I find a little time, I try to move the config out of the project (so that it's loaded dynamically). With that, community specific changes can be maintained independently of coding changes which allows good PRs as well as building releases.

xf- commented 1 year ago

Yeah, our solution is pretty old and meshviewer changed a lot over time (e.,g. new URL router) and APIs in browsers changed and improved. A new script would be a nice way or rewrite the hole thing to get rid of the iframe. In general a rewrite would be welcome. A lot of stuff is pretty much outdated or uses an old version. https://github.com/requirejs/almond

This comment captures it pretty well https://github.com/requirejs/almond/issues/118