Death to Flashy
Let me tell you a tale about an initiative to reduce Flash dependencies on what used to be a "Flash gaming" site. This is the story about a large-scale project with the best possible outcome being that users don't notice any changes, and the worst possible outcome of everything breaking horribly. This, my friends, is the story of Kongregate's Death to Flashy initiative.
In the Beginning
When we first started building Kongregate, we didn't have to worry much about whether or not users had Flash installed. It was very much ubiquitous, and since you couldn't play most of the games on the site without it we could build features based on the assumption that Flash was available. We built our chat client in Flash, we created APIs for both ActionScript 2 and 3, games implemented them, and all was good.
For a very long time, the vast majority of the games on Kongregate were made with Flash, even most of our externally hosted "iframe" games. Some of these external games eventually needed a way to interact with Kongregate via JavaScript, however.
The "JavaScript" API
When it came time to create a JavaScript interface to our API, we took the most technically expedient path. The idea was to create a JavaScript wrapper (using Adobe's FABridge) that could expose the already-written ActionScript 3 API methods to JS. This made sense at the time, since a relatively small number of games needed this functionality. The downside was obviously that players still needed Flash in order for the API to work, even if the game itself did not utilize Flash. Since a huge majority of our players had a recent Flash Player this was seen as a reasonable tradeoff.
Writing on the Wall
Kongregate's Flash API and chat functionality use invisible "helper" Flash objects embedded into the page. You can't see them, but they are doing meaningful work so that you can enjoy playing your games. The problem with allowing these invisible SWF files to run is that not everyone has pure intentions. Advertisers could use similar techniques to help track users without their knowledge. Browser vendors want to stop these practices, and announced a plan to start doing so, which meant we had to start thinking about migrating a ton of our code from ActionScript 3 to JavaScript.
Once Chrome and Firefox started blocking these invisible SWFs we had to start serving them from our top-level domain as opposed to our CDN, as SWF files from the same domain as the parent page do not get automatically blocked. This temporary workaround gave us some breathing room, but it will be removed from major browsers in the near future (it has just recently been removed from Chrome), and definitely will not work anymore once Adobe puts the final nail in the Flash coffin.
Phase 1: Migrating Chat
While our chat UI was already built with JavaScript, the connection and parsing logic were all built using ActionScript 3 back in 2009. In June of 2016 we started the long journey of porting all the relevant code to JavaScript while aiming to still be compatible with IE8. The connection logic was modified to connect via WebSockets and fail over to BOSH as needed.
The original chat code used some ActionScript-specific functionality (like serializing and compressing user data into an AS3-specific binary format), so we had to do some intermediate releases to resolve those issues and make things forward-compatible so that Flash and JS users could see each other as we rolled the feature out.
We added in the ability to progressively enable the feature for players, and that turned out to be key. Whenever we discovered a new bug (of which there were plenty), we could flip a kill switch and all users would be back on the Flash version of chat until we could resolve the problem.
Since IE8 and IE9 were supported browsers that don't have WebSockets available, we decided to use a Flash WebSocket polyfill for them. This technically introduced more Flash into our stack, but was seen as a good tradeoff since it was a black box 3rd party component, and it meant that all browsers would be using the new JS code path as opposed to IE8 and IE9 using the old AS3 component.
Near the end of 2016 we had completely opened the floodgates, and if you have been chatting on Kongregate since then you have been using the new HTML5 chat system, hopefully without knowing it. One advantage of the new WebSocket-based chat is that it connects using standard HTTPS over port 443, making it much less likely to be blocked by a firewall or antivirus software.
Phase 2: Creating a Real JavaScript API
The fact that we did not have a pure JavaScript API had bugged me for years. However, since it worked fine there never seemed to be a good reason to take the time to re-write it. That all changed when HTML5/WebGL games started gaining popularity, and the number of users with Flash either not installed or disabled by default started growing. You shouldn't need Flash enabled to be able to play a pure HTML5 game on Kongregate.
The Kongregate API consists of two major components -- the "client," which is the part that games interact with directly, and the "Konduit," which is a SWF that is loaded in the top-level page and can act on behalf of the user. This phase of the project was logically split up into porting each of those components.
The top priority for rewriting the API was ensuring that developers would not notice a difference, and that their games would continue to work. We started with porting the "Konduit" component, as games don't interact with that part directly anyway.
For each logical component of the API (services, stats, etc.), we made sure that each request from each game would receive the same response with the new code. Piece by piece we ported logic from the Flash/AS3 Konduit component into JavaScript, and accessed it via ExternalInterface when requests came in from the API client, until the Konduit SWF was nothing more than a shell that forwarded incoming requests to JS and relayed the response back.
Since ActionScript and JavaScript are both ECMAScript-based languages, porting between them is not inherently difficult. However, ActionScript provides a very nice standard library for networking, saved data, image processing, and other features that we were using. We had to spend a decent amount of time converting these to their JS equivalents -- converting network calls to use XMLHttpRequest
, SharedObject
to LocalStorage
, etc.
Once the Konduit logic was fully ported, it was time to start on the API client. The biggest hurdle for this part of the project was switching from Adobe's LocalConnection class to PostMessage for cross-frame communication. We had been plagued with LocalConnection
issues and bugs for years (if you've ever had trouble being awarded a badge LocalConnection
is likely the culprit), so this was a very welcome change.
The client side of the API actually does not perform a ton of logic, but mostly sends messages to the Konduit for processing. Once we had the message transport working in JavaScript via PostMessage
the rest of the work went relatively quickly.
Once again we added in mechanisms to roll this out to users slowly so that we could revert to the previous behavior when we found issues, which definitely came in handy, but also increased QA overhead, as our test game matrix had to be checked with the new features both enabled and disabled.
One interesting bug that came up was that the JavaScript API actually loaded too quickly compared to the old Flash-based one, resulting in the "loaded" callback being called in the same JavaScript "tick." This broke some games because they were not expecting the callback at that point, so we had to add in a slight delay and ensure the callback was truly asynchronous to fix them.
For the past 5 months or so all users have been using the JavaScript implementation of the API for HTML5 games, which means that you can totally disable Flash Player and still chat and earn badges while playing those titles.
Phase 3: What about Flash Games?
So now that our non-Flash games are in good shape, what about Flash? Even though Adobe has announced an official end-of-life date for Flash at the end of 2020, we feel it is important to keep supporting these games as long as it is possible to play them.
At this point in the project all Flash games were still using the ActionScript client API, which makes sense, but is a bit problematic because it requires the Konduit helper SWF to be present in order to listen for messages over the always-problematic Flash LocalConnection
class. This will become even more of a problem in several months when Chrome stops allowing invisible SWF helpers to run automatically and requires user interaction (such as a click) to activate them.
This phase of the project focused on making Flash games send API messages using PostMessage
via ExternalInterface
where possible, so that everything would work even if the Konduit SWF is not present or active. For games hosted by Kongregate, this was mostly trivial since we control the hosting page. We just needed to load the games inside an iframe, ensure the JavaScript API was present, and set allowScriptAccess
to always
for the embedded SWF files. For externally hosted games, this will only work if the developer happened to set allowScriptAccess
to always
, which many of them do since it grants them access to JavaScript and the enclosing page.
Once the API SWF loads, it checks to see if the ExternalInterface
is available. If so, it checks for the existence of the Kongregate JS API. If it is not present, it loads it asynchronously, and then notifies the Flash API that it is ready for use, at which point it completes initialization and configures itself to use PostMessage
as opposed to LocalConnection
as a transport.
This phase of the project has been rolled out and is active for 100% of games. We are currently tracking externally hosted Flash games that fail to connect via the JavaScript API, and we will be reaching out to developers to help them resolve any issues before Flash becomes click-to-activate so that these games can continue to run smoothly.
What's Next?
Since completing the DTF project our support requests for both chat connections and missing badges have been noticeably reduced, which is a fantastic side effect.
We are actively working on squashing the remaining backward-compatibility bugs as issues arise. Our QA team has been working extremely hard testing hundreds of games to make sure they still work properly with all of these changes. To give you an idea of the kinds of things we have run into, one developer happened to be creating a top-level function named addEventListener
(essentially overwriting window.addEventListener) and breaking our ability to communicate via PostMessage
. The developer did nothing wrong here, and everything worked fine until we happened to need to use that method for the JavaScript API. The workaround for this issue involved calling addEventListener
on the document
object and applying that to the Window
, which of course does not work in IE. The IE fix required us to create a temporary iframe object and apply that element's addEventListener
function to the Window
.
We currently have the "client" API logic duplicated in AS2, AS3, and JavaScript. It would be great to clean that up so that modifying the API becomes less painful. At that point, the API will have come full circle from being a JavaScript wrapper around ActionScript, to essentially an ActionScript wrapper around JavaScript. This is not currently a high priority since the API is fairly stable, but we will take this into consideration the next time a major change needs to be made.
Thanks for reading along, and stay tuned for our next adventure -- "Welcome to SS-Hell!"