{"action":"created","issue":{"url":"https://api.github.com/repos/bemson/subetha-bridge/issues/1","labels_url":"https://api.github.com/repos/bemson/subetha-bridge/issues/1/labels{/name}","comments_url":"https://api.github.com/repos/bemson/subetha-bridge/issues/1/comments","events_url":"https://api.github.com/repos/bemson/subetha-bridge/issues/1/events","html_url":"https://github.com/bemson/subetha-bridge/issues/1","id":52545537,"number":1,"title":"Supporting IE","user":{"login":"bemson","id":352402,"avatar_url":"https://avatars.githubusercontent.com/u/352402?v=3","gravatar_id":"","url":"https://api.github.com/users/bemson","html_url":"https://github.com/bemson","followers_url":"https://api.github.com/users/bemson/followers","following_url":"https://api.github.com/users/bemson/following{/other_user}","gists_url":"https://api.github.com/users/bemson/gists{/gist_id}","starred_url":"https://api.github.com/users/bemson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bemson/subscriptions","organizations_url":"https://api.github.com/users/bemson/orgs","repos_url":"https://api.github.com/users/bemson/repos","events_url":"https://api.github.com/users/bemson/events{/privacy}","received_events_url":"https://api.github.com/users/bemson/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/bemson/subetha-bridge/labels/help%20wanted","name":"help wanted","color":"159818"}],"state":"open","locked":false,"assignee":{"login":"bemson","id":352402,"avatar_url":"https://avatars.githubusercontent.com/u/352402?v=3","gravatar_id":"","url":"https://api.github.com/users/bemson","html_url":"https://github.com/bemson","followers_url":"https://api.github.com/users/bemson/followers","following_url":"https://api.github.com/users/bemson/following{/other_user}","gists_url":"https://api.github.com/users/bemson/gists{/gist_id}","starred_url":"https://api.github.com/users/bemson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bemson/subscriptions","organizations_url":"https://api.github.com/users/bemson/orgs","repos_url":"https://api.github.com/users/bemson/repos","events_url":"https://api.github.com/users/bemson/events{/privacy}","received_events_url":"https://api.github.com/users/bemson/received_events","type":"User","site_admin":false},"milestone":null,"comments":21,"created_at":"2014-12-20T01:01:34Z","updated_at":"2015-10-30T03:45:03Z","closed_at":null,"body":"After a slow bout of not-well documented testing, I've answered the following questions involving iframes, different domains, and localStorage. The questions address functionality that a browser platform *must* provide, in order to support these SubEtha features:\r\n\r\n  1. Observe localStorage data & changes.\r\n    * Storage events are the _message bus_, upon which SubEtha stakes it's claim.\r\n  2. Observe iframe unloading.\r\n    * Unloading is key to the SubEtha bridge, since it must update the client map (a localStorage key) and notify the network of any departing peers.\r\n\r\n#### Can the iframe access data in same origin?\r\n\r\n  * Safari - no :x:\r\n  * Opera - yes :heavy_check_mark: \r\n  * Chrome - yes :heavy_check_mark: \r\n  * Firefox - yes :heavy_check_mark:\r\n  * IE 8 - yes :heavy_check_mark:\r\n  * IE 9 - yes :heavy_check_mark:\r\n  * IE 10 - yes :heavy_check_mark:\r\n  * IE 11 - yes :heavy_check_mark:\r\n\r\nSafari turned out to be the odd-browser out. Safari's default settings produce a kind of sandboxed storage that isolates iframed pages, even when pointing to the same origin. This behavior is due to user settings, and has no apparent workaround.\r\n\r\n#### Can the iframe receive storage events?\r\n\r\n  * Safari - no :x:\r\n  * Opera - yes :heavy_check_mark: \r\n  * Chrome - yes :heavy_check_mark: \r\n  * Firefox - yes :heavy_check_mark:\r\n  * IE 8 - yes :heavy_check_mark:\r\n  * IE 9 - yes :heavy_check_mark:\r\n  * IE 10 - yes :heavy_check_mark:\r\n  * IE 11 - no :x:\r\n\r\nSafari results are again blamed on the default user setting. All versions of IE require a workaround.\r\n\r\n[IE 8-10 provide empty storage events](http://webreflection.blogspot.com/2009/08/w3-html5-storage-what-works-what-does.html) with zero relevant details. However, their occurrence is enough to manually track of changes, vs. receiving them in the event.\r\n\r\n[IE 11 does not provide storage events](https://connect.microsoft.com/IE/feedback/details/811546/ie11-localstorage-events-fire-twice-or-not-at-all-in-iframes) to different domain iframes. Any solution will require polling.\r\n\r\n#### Does the iframe receive an unload event when the parent navigates?\r\n\r\n  * Safari - yes :heavy_check_mark: \r\n  * Opera - yes :heavy_check_mark: \r\n  * Chrome - yes :heavy_check_mark: \r\n  * Firefox - yes :heavy_check_mark:\r\n  * IE 8 - yes :heavy_check_mark: \r\n  * IE 9 - yes :heavy_check_mark: \r\n  * IE 10 - yes :heavy_check_mark: \r\n  * IE 11 - yes :heavy_check_mark: \r\n\r\nMercifully, all browsers trigger the unload event when the parent page navigates.\r\n\r\n#### Does the iframe receive an unload event when the node is removed from the parent DOM?\r\n\r\n  * Safari - yes :heavy_check_mark: \r\n  * Opera - yes :heavy_check_mark: \r\n  * Chrome - yes :heavy_check_mark: \r\n  * Firefox - yes :heavy_check_mark:\r\n  * IE 8 - no :x:\r\n  * IE 9 - yes :heavy_check_mark: \r\n  * IE 10 - yes :heavy_check_mark: \r\n  * IE 11 - yes :heavy_check_mark: \r\n\r\nIE 8 simply does not provide this event, when the parent DOM removes the bridge's iframe. Having the parent monitor the DOM is not a solution, since it is the bridge (i.e., iframed document) that must observe the unload event, in order to perform it's clean up routine. This finding effectively eliminates IE 8 as a SubEtha compatible browser.\r\n\r\n## Next steps?\r\n\r\nGiven my tests, it's clear that Safari and IE 8 can not support SubEtha. As well, workarounds must be coded for IE support. This bug will track progress in developing the IE workarounds."},"comment":{"url":"https://api.github.com/repos/bemson/subetha-bridge/issues/comments/152406583","html_url":"https://github.com/bemson/subetha-bridge/issues/1#issuecomment-152406583","issue_url":"https://api.github.com/repos/bemson/subetha-bridge/issues/1","id":152406583,"user":{"login":"ryangiomi","id":931800,"avatar_url":"https://avatars.githubusercontent.com/u/931800?v=3","gravatar_id":"","url":"https://api.github.com/users/ryangiomi","html_url":"https://github.com/ryangiomi","followers_url":"https://api.github.com/users/ryangiomi/followers","following_url":"https://api.github.com/users/ryangiomi/following{/other_user}","gists_url":"https://api.github.com/users/ryangiomi/gists{/gist_id}","starred_url":"https://api.github.com/users/ryangiomi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ryangiomi/subscriptions","organizations_url":"https://api.github.com/users/ryangiomi/orgs","repos_url":"https://api.github.com/users/ryangiomi/repos","events_url":"https://api.github.com/users/ryangiomi/events{/privacy}","received_events_url":"https://api.github.com/users/ryangiomi/received_events","type":"User","site_admin":false},"created_at":"2015-10-30T03:45:02Z","updated_at":"2015-10-30T03:45:02Z","body":"Great write up, and congrats!  Had a laugh about IE and cowballs.  :D\n________________________________\nFrom: Bemi Faison<mailto:notifications@github.com>\nSent: ‎10/‎29/‎2015 20:27\nTo: bemson/subetha-bridge<mailto:subetha-bridge@noreply.github.com>\nCc: ryangiomi<mailto:ryangiomi@live.com>\nSubject: Re: [subetha-bridge] Supporting IE (#1)\n\n# The Tao of LocalStorage and IE\n\n\nIt has been an uncomfortably long while since updating this thread. After so much support and interest from peers, it felt prudent to capture something now, before addressing the next challenge. So, forgive me in advance for this laborious review and reflection of the past ten odd months of frustratingly slow progress.\n\n\n## The Demo: Counting Together\n\n\nAs one would hope of any self-respecting technology article, there is [a SubEtha Bridge demonstration](http://jsbin.com/kemosi). The demo is very rudimentary: rendering but a single number on the page. That number reflects how many _other_ bridges are on the veritable localStorage \"network\"... Riveting stuff, isn't it?\n\nTo see it in action, load the url in multiple windows (tabs, are fine, but far less interesting). As you open and close them, each window will increment and decrement it's number. Latency aside, the synchronized updating of separate windows shows how they are in fact linked.\n\nHere is a short screen capture of the demo, using four IE 10 windows.\n\n![SubEtha Bridge demo screen capture](http://i.imgur.com/nhlahNw.gif)\n\n\n### Technical Errata\n\n\nThough it uses my Bridge module, the demo is meant to example how localStorage can be used in IE, for inter-window communications. The demo code actually co-opts [a specific version of the Bridge module](https://github.com/bemson/subetha-bridge/blob/deedc1f57bcf0ed9596656a84ef3e2f9cf24baf8/src/subetha-bridge.js), and fools it to work outside an iframe and with a mock [Client module](/bemson/subetha-client).\n\nBecause this demo is _not_ in an iframe, it should work in Firefox and Safari browsers. However, the larger [SubEtha project](/bemson/subetha) will continue to target Chrome, IE 10, and IE 11 only. (Respectively, by policy and default, Firefox and Safari do not allow storage access in third-party iframes.)\n\nIE Edge was the outlier for this solution. The approach _may_ work with non-iframed, same-origin windows in IE Edge. Unfortunately, for my purposes, IE Edge is a lost cause...\n\n\n## The Problem: Uneventful Events\n\n\n[Storage events](http://html5demos.com/storage-events) offer the ability for separate browser instances to observe changes to their shared, origin-based localStorage items. The event would describe when and how a given item (or \"key\") changed. Withstanding Firefox and Safari's paranoid security policies, IE is the only browser to get this wrong.\n\nIE's [infamous](http://webreflection.blogspot.com/2009/08/w3-html5-storage-what-works-what-does.html), [abysmal](https://connect.microsoft.com/IE/feedback/details/811546/ie11-localstorage-events-fire-twice-or-not-at-all-in-iframes), storage event [implementation](https://connect.microsoft.com/IE/feedback/details/812563/ie-11-local-storage-synchronization-issues) provides zero information on the nature of a storage change. At best, IE storage events inform you that a change occurred. The result is out of sync logic and an impossibly disconnected \"network\".\n\n\n## The Solution: Treat LocalStorage Like a Hash\n\n\nWithout useful storage events in IE, localStorage should be treated as the mundane (albeit persisted) hash object it always was. Normal best-practices can be applied, as if you were dealing with any old shared object. Also, considering that this hash will be touched by several untrusted routines, you might then imagine measures to monitor changes made by other routines.\n\nOnce it became clear that I could never artificially broadcast localStorage changes to other windows, figuring out a way to communicate became more straightforward (less \"feature magic\" from the browser). I used key additions, updates and removals, to send messages between windows.\n\n\n### Proper Namespacing Applies\n\n\nIn IE, for any routine that uses localStorage, you should use standard namespacing. The difference is that each namespace would need to be wholly unique, in order to avoid sharing keys (akin to namespace collisions). In my module, bridges generated a unique/random id, and prefixed their storage items with that id.\n\nBy not sharing keys, you avoid data racing. A [data race](http://stackoverflow.com/a/11276360) is similar to a [race condition](https://en.wikipedia.org/wiki/Race_condition), except it's the data which is not concurrent (not the execution). For instance, multiple windows _writing_ to the same key will leads to data loss, since each window can only read the latest value.\n\n> The magic of storage events ameliorated these data race/loss scenarios. Logic no longer relied on the current value, but transitional one provided by the storage event.\n\n\n### Monitor Changes Manually\n\n\nWith localStorage - an ever changing shared hash - you will need to observe object member changes yourself. However you implement it (i.e., the events you define and their payloads), this boils down to periodically reconciling keys with some cached value.\n\nThat effort led to a monitoring routine and some custom events. My work was tweaked for my module, but it could be [extracted as a separate module](https://github.com/bemson/subetha-bridge/blob/deedc1f57bcf0ed9596656a84ef3e2f9cf24baf8/src/subetha-bridge.js#L256-L604).\n\nMonitoring any object this way is absolutely non-performant. But it does offer the next best thing to native \"storage events\". The primary difference being that you can only catch _some_ changes with this method - storage events (are meant to) capture _every_ transition of a storage item.\n\nSo, to measure bad and good news, this is not a way to monitor _fast_ changing storage items. Nor does it guarantee that a changed or removed item will be observed at all. More so, the term \"fast\" is relative, and complicated by browser-specific i/o details that I loathe to dive into here. (Not that I know about them, mind you - just that low-level details always have a long tail.)\n\nUltimately, in order for this error prone approach to work, you'll need to adjust _how_ you use localStorage entirely...\n\n\n### Use the Hash Like a Log\n\n\nWith a poor-mans object monitor, it's best to change values as little as possible. So instead of capturing an object in localStorage, capture changes to the object's state. Said another way, you want to _capture_ your custom storage events (that you don't receive in IE) in localStorage.\n\nFor example, here's how you might trace the on/off state of a light-bulb over time. Instead of making a key reflect the bulb's _current_ state, you would add keys which reflect changes to that bulbs state.\n\n\n| Key  | Value  |\n|---|---|\n| `light-<guid>`  | `{\"watts\": \"60\", \"room\": \"den\", \"created\": 12310920}`   |\n| `light-<guid>-change-1`  | `{\"on\":1,\"ts\":12345678}`   |\n| `light-<guid>-change-2`  | `{\"on\":0,\"ts\":12346678}`  |\n| `light-<guid>-change-3`  | `{\"on\":1,\"ts\":12347678}`   |\n| `light-<guid>-change-4`  | `{\"on\":0,\"ts\":12348678}`   |\n\n\nBy designating a key-naming pattern that identifies the bulb's static characteristics, you can effectively communicate it's dynamic characteristics to other windows. In this example, changes to the on/off state are sequenced in the key name. The actual value further communicates the timing of the state change.\n\nWith this approach, any slower performing windows can catch up to every transition you expected with normal storage events.\n\n\n#### Sort and Prioritize Custom Notifications\n\nRegarding key-naming patterns, it's important to use a scheme that prioritizes the messages you want to communicate between two windows. Continuing with the bulb example, a bulb that breaks, might use the key `break-<guid>`, where the actual \"guid\" matches a key named `light-<guid>`. Before or after reconciling which keys changed, you would then process broken bulb events before bulb change events. The importance of these details depend on your goals.\n\n\n#### Devise a Protocol for Value Changes and Key Removal\n\n\nThis approach doesn't mean never remove a key. But it does mean that you want to design how your system should respond when certain keys are removed. Some keys won't matter - like the bulb state change keys. Some would - like the bulb identity keys.\n\nIn my case, I used the removal of a value to communicate meaning to other windows. My particular IndexedDB cleanup process required the aid of the remaining Bridges.\n\nWhen a window closes, the Bridge's identity key is set to \"0\". Other bridges see this, then line up to determine who will clean up the expired bridge. Once one of those bridges finish, it removes the identity key of the expired bridge. The removal of the key informs the bridges that the deed is done.\n\nIn this way, I update a bridge's value once to communicate a binary state change: alive or dead. Then I use the removal of the key to further communicate to bridges that the expired bridge has been cleaned up.\n\n\n### IE LocalStorage Gotchas\n\n\nWhile this strategy for addressing IE's localStorage shortcomings works, there are a few surprises that I wanted to relay as well.\n\n\n#### Mind the Gap\n\n\nIE 10 and IE 11 are completely different, when it comes to localStorage events: One has them, the other doesn't. My poor shim had to use different techniques for assessing data changes.\n\nWith IE 10, I let the events trigger scans. However, since each write results in several events, I chose to [debounce](http://davidwalsh.name/javascript-debounce-function) them a bit. I will likely [throttle](http://sampsonblog.com/749/simple-throttle-function) them in the future, to avoid having perpetual events preventing otherwise timely scans.\n\nIn IE 11, there are no events to speak of, so I simply scan localStorage at a reasonable interval. Because scans are costly, I plan to make that interval dynamic, based on the frequency of changes encountered.\n\n\n#### Calling all Keys\n\n\nI must warn against these two methods of reading localStorage keys in IE: `Object.keys(localStorage)`, and `for (key in localStorage) {...}`. Using either will likely result in missing keys that have been added by other windows.\n\nThe safest way to retrieve all localStorage keys/values, is to use the `.length` property along with the `.key()` and `.getItem()` methods.\n\n```\nvar\n  ln = localStorage.length,\n  cache = {},\n  key\n;\n\nwhile (ln--) {\n  key = localStorage.key(ln);\n  cache[key] = localStorage.getItem(key);\n}\n```\n\n\n#### Raise Your Hand if You're _Not_ Here\n\n\nEven with the recommended approach for reading data, I've been able to retrieve a key who's value is `null`. The ability to read _deleted_ keys, is perhaps the most notable, damning evidence that IE's localStorage implementation sucks cow balls.\n\nSo don't assume that a key's existing means it is valid. A simple string-type check should separate the dead from the living. This will avoid emitting an update-like event on keys that have actually removed.\n\n\n#### Drop Kick IE Often\n\n\nThe most surprising discovery came from observing that some windows simply _stopped_ observing localStorage changes. Like forcing a CSS redraw, localStorage must also be kicked in to gear, so it's data stays in sync.\n\nThough an expensive operation, syncing was mercifully simple: just write something to localStorage. Thankfully, so far, I've only observed this in IE 10.\n\n```\nfunction ieKickStorage() {\n  localStorage.setItem('somekey', 1);\n}\n```\n\nBased on my experiments, neither the key nor value has to change between writes - which is somewhat counterintuitive. I'm unsure if removing a key would have the same effect.\n\nIn my Bridge module, I \"kick\" localStorage every 3 seconds with the same key and value. I plan to add some finesse this, like resetting my interval after actually writing data.\n\n\n## What's Next?\n\n\nFirstly, this issue is not _solved_ in my opinion. However, my progress does allow moving on to other topics. The lack of IE Edge support will likely be addressed separately as a new bug. (Hasn't this one been through enough?)\n\nSecondly, I'd like to put time towards a demo that doesn't include my Bridge module. It's really a distraction from what was learned here. By extracting my localStorage monitor, one could easily write logic and a key structure that monitors the addition and removal of windows... Any takers?\n\n\n### What About SubEtha?\n\n\nI grew fond of the diagrams I created in this thread, and have updated them - but they mainly describe SubEtha, not the Bridge. I hope to post them in the [SubEtha wiki](https://github.com/bemson/subetha/wiki) (which is empty now).\n\nI also need to update the [Client module](/bemson/subetha-client) to work with the new Bridge module. While these tasks require time, neither should take too long.\n\n\n### Gratitude & Acknowledgments\n\n\nThanks to the following individuals for seeing me through this insane issue. At some critical moment, these folks offered their time and attention, if not enthusiasm and interest in this issue. For that, I thank you - in order of your user name:\n\n  * @aversini\n  * @bossroller\n  * @cnojima\n  * @davidrekow\n  * @gothy\n  * @puzrin\n  * @reptile\n  * @ryangiomi\n  * @serpentjoe\n  * @tejacques\n\n\n---\nReply to this email directly or view it on GitHub:\nhttps://github.com/bemson/subetha-bridge/issues/1#issuecomment-152403634\n"}}