Tuesday, 1 September 2009

Creating modular applications in Adobe AIR

Identity is on hold at the moment, work still being too hectic. I'm hoping to really get down to it next year - I've got 3 O'Reilly books on the way out by March next year, and they're eating all my time.

In the meantime, the main work project I've got going on is a nice fit with what Identity needs to achieve in terms of modularity. It's an e-learning app, already running successfully as an Air application, but we're rebuilding it to achieve the following:

1) Dynamically built modular environment. Other than the AIR shell itself, all the functionality will be loaded by the app at runtime. This allows for non-admin updates, and for user A and user B to have different logging systems / quiz engine etc.

2) Reskinned at runtime. Different departments and sub companies of the corporate client can have a different look and feel, maybe even a different screen size requirement.

I've already grappled with the Air security sandbox stuff within this app. The lessons themselves are dynamically loaded and using sandbox bridge for communication. The current quiz apps are also decoupled, and only simple data is passing around - mostly I'm using strings of xml for more structured data requirements.

I've been playing with the two variations of loading content into flash:

A) The light side: use a normal loader, put your downloaded content into a sandbox where it can only access other content via the safe and secure bridge.

B) The dark side: read the file into a ByteArray and use loadBytes() with allowLoadBytesCodeExecution = true on the loader context, to give your loaded content the run of the place... including the fileSystem.

The pros and cons of breaking the Air security model

The light side:

Events
Events must be passed using loaderInfo.sharedEvents. Only built-in events keep their type, but that's ok. The actual Event.type property is just a plain string anyway, so you can still make use of a custom event for compile time checking to avoid typos, as the Air shell doesn't care that it didn't know what MyCustomEvent.DOG_BARKED was - it just sees "dogBarked". On the down side you can't pass useful extra data with your custom events.

Security
You can use sensible functions like storeAsset(assetPath:String) to pull in things and read / write to the file system, rather than opening up your user's computer for an attack by rogue dynamically loaded modules.

Loading grandchildren
Loading a grandchild asset such as a png or jpg seems to be possible using loadBytes() with code execution set to false. You have to pass the byteArray as a normal array, reconstruct it to be a byteArray, and then load the content in the child module. I've only part tested this. More to follow. And it's pretty gross as a process.

Decoupling logic and events is ok
Modularising the logic of the application doesn't actually seem to be too hard. The code was pretty nicely decoupled already, and we think we can reduce inter-module communication to a combination of vanilla events and xml (passed as string). We'd use the Air shell as a postman to deliver messages from one module to another. There would be an unnerving amount of dynamic stuff which isn't checkable at compile time, but it's doable.

Major hurdle: providing skin assets at runtime
I simply can't find any way to load the actual graphics for the modules at runtime. All I want is to load a specific (dynamically selected) swf with a bunch of MCs in the library, and to be able to do stuff like "new LoginButtonUpSkin()" and whack the requested graphic into the right place. I guess we could export all of our graphics as pngs and use the loadBytes() trick to feed them to the individual modules, but how grim is that?

The other option would be to compile variations on the modules themselves, with different look and feel. "MainMenuGreen.swf" "MainMenuBlue.swf". That feels like a huge pain in the ass when it comes to creating new skins. The designers would need access to the code library, they always ring me up wondering how to link their files to the com folder... you can see the potential headaches I'm sure...

The dark side:

Events: Module to module event traffic is possible. Events don't lose their typing when passing from module to module.

Security: This is dangerous stuff. But all my modules are coming from a trusted server and there's a technique for verifying signatures. Basically any module that wants to be loaded into the application sandbox using the loadBytes() method would have to be an Air app in itself. Then that air package gets loaded and ripped open, the signature verified and the module loaded or rejected. I've no doubt that it's not failsafe.

Will adobe continue to provide this work-around?
The adobe official documentation on working securely with untrusted content end with the following fear-inducing statement:

Note: In a future release of Adobe AIR, this API may change. When that occurs, you may need to recompile content that uses theallowLoadBytesCodeExecution property of the LoaderContext class.

A future release WHEN? What? And will I need to recompile content even if it's happily running. Will Air 2.0 break my 1.5 application if I've used this? Presumably Adobe would provide a better work around for achieving this, but my client doesn't want to do a thousand admin-requiring reinstalls... which is why we're going to use a module based system in the first place...

At the moment I'm still testing, still lurching from favouring one side to the other. More to follow as it unfolds. Hopefully I can save someone the three days of googling and testing I've just been through.

1 comment:

Tim said...

Thanks for summarizing all of this. I haven't had to deal with the problem yet but have thought about it were I to build an application which follows the same approach.