480 likes | 628 Views
OpenSocial Across Containers. Arne Roomann-Kurrik Lane LiaBraaten May 28, 2008. Write Once, Run Anywhere?. Make Your App Robust. Make Your App Robust. Be explicit!. Make Your App Robust. Check for errors!. Make Your App Robust. Check for support!. Robustness - Be Explicit.
E N D
OpenSocialAcross Containers • Arne Roomann-Kurrik • Lane LiaBraaten • May 28, 2008
Make Your App Robust Be explicit!
Make Your App Robust Check for errors!
Make Your App Robust Check for support!
Robustness - Be Explicit • Spot the bug in this code! Let's request the owner: • And access their profile URL: function request() { var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest( opensocial.DataRequest.PersonId.VIEWER), "req"); req.send(response);}; function response(data) { var url = data.get("req").getData().getField( opensocial.Person.Field.PROFILE_URL)); ...}
Robustness - Be Explicit • On hi5, url is: http://www.hi5.com/friend/profile/ displayProfile.do?userid=XXXXXXXXXX • On orkut, url is: null • The OpenSocial specification only states the following about fields available on Person objects: "The server will always include ID, NAME, and THUMBNAIL_URL."* *http://rurl.org/qoo
Robustness - Be Explicit • Introducing opensocial.DataRequest .PeopleRequestFields.PROFILE_DETAILS • Assign an array of properties you want to access to this optional parameter var params = {}; params[opensocial.DataRequest .PeopleRequestFields.PROFILE_DETAILS] = [ opensocial.Person.Field.ABOUT_ME, opensocial.Person.Field.ADDRESSES, opensocial.Person.Field.AGE ];
Robustness - Be Explicit • Fixing the request: From this... function request() { var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest( opensocial.DataRequest.PersonId.VIEWER), "req"); req.send(response);};
Robustness - Be Explicit • ...to this: function request() { var req = opensocial.newDataRequest(); var params = {}; params[opensocial.DataRequest.PeopleRequestFields .PROFILE_DETAILS] = [ opensocial.Person.Field.PROFILE_URL ]; req.add(req.newFetchPersonRequest( opensocial.DataRequest.PersonId.VIEWER, params), "req"); req.send(response);};
Robustness - Be Explicit • On hi5, url is: http://www.hi5.com/friend/profile/ displayProfile.do?userid=XXXXXXXXXX • On orkut, url is: http://www.orkut.com/ Profile.aspx?uid=YYYYYYYYY
Robustness - Check For Errors • Why is the following code brittle? Request a user by ID: function request() { var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest( "14088281537290874435"), "req"); req.send(response);}; • And get the display name: function response(data) { var name = data.get("req").getData() .getDisplayName(); ...};
Robustness - Check For Errors • If the passed ID is invalid for any reason, the previous code throws a JavaScript error: data.get("req").getData() has no properties • Check for problems! All callbacks get arguments withhadErrormethods: function response(data) { if (!data.hadError()) { var name = data.get("req").getData() .getDisplayName(); ... }};
Robustness - Check For Errors • Subrequests also have hadError methods: function response(data) { if (!data.hadError()) { ... } else { if (data.get("req").hadError()) { ... } }}; • You can check each request individually to see where the point of error is.
Robustness - Check For Errors • Handling errors gracefully: function response(data) { if (!data.hadError()) { ... } else if (data.get("req").hadError()) { alert(data.get("req").getErrorMessage()); } else { alert("An unknown error occurred"); }}; • getErrorMessage() provides a human-readable error message, but varies by container. • This may not be ideal for display to an end user.
Robustness - Check For Errors • Check for error types at runtime: function response(data) { if (!data.hadError()) { ...} else if (data.get("req").hadError()) { switch (data.get("req").getErrorCode()) { case opensocial.ResponseItem.Error.BAD_REQUEST: ... break; case opensocial.ResponseItem.Error.INTERNAL_ERROR: ... break; } } else { ...
Robustness - Check For Errors • opensocial.ResponseItem.Error.BAD_REQUEST • The request was invalid • opensocial.ResponseItem.Error.FORBIDDEN • The gadget can never have access to this data • opensocial.ResponseItem.Error.INTERNAL_ERROR • Server problem • opensocial.ResponseItem.Error.LIMIT_EXCEEDED • Over quota • opensocial.ResponseItem.Error.NOT_IMPLEMENTED • No container support • opensocial.ResponseItem.Error.UNAUTHORIZED • The gadget does not have access to this data
Robustness - Check For Support • Containers have different purposes. Not every field may be available. opensocial.Person.Field.LOOKING_FOR • hi5: not supported • MySpace: not supported • orkut: supported MyOpenSpace.Person.Field.DESIRE_TO_MEET • hi5: not supported • MySpace: supported • orkut: not supported hi5.PersonField.PRESENCE • hi5: supported • MySpace: not supported • orkut: not supported
Robustness - Check For Support • Your impulse may be to write code like this: if (MySpace) { ...} else if (hi5) { ...} else if (orkut) { ...} else { ... } • This is brittle! How do you scale to new containers automatically?
Robustness - Check For Support • Sounds like a job for capabilities querying: var supports_lookingfor = opensocial.getEnvironment().supportsField( opensocial.Environment.ObjectType.PERSON, opensocial.Person.Field.LOOKING_FOR); • Now supports_lookingforhas the following value: • on hi5: false • on MySpace: false • on orkut: true • Goal: Build apps that programatically utilize extra functionality based on such booleans • Why is this approach brittle?
Robustness - Check For Support • Try a container-specific profile field: var supports_desiretomeet = opensocial.getEnvironment().supportsField( opensocial.Environment.ObjectType.PERSON, MyOpenSpace.Person.Field.DESIRE_TO_MEET); • Now supports_desiretomeethas the following value: • on hi5: JavaScript error • on MySpace: true • on orkut: JavaScript error • Oops!
Robustness - Check For Support • What about: var supports_desiretomeet = false; if (MyOpenSpace) { var supports_desiretomeet = opensocial.getEnvironment().supportsField( opensocial.Environment.ObjectType.PERSON, MyOpenSpace.Person.Field.DESIRE_TO_MEET); } • Now supports_desiretomeethas the following value: • on hi5: false • on MySpace: true • on orkut: false • This approach defeats the purpose of querying!
Robustness - Check For Support • You should be able to see that: var supports_desiretomeet = false; if (MyOpenSpace) { var supports_desiretomeet = opensocial.getEnvironment().supportsField( opensocial.Environment.ObjectType.PERSON, MyOpenSpace.Person.Field.DESIRE_TO_MEET); } • Is really the same as: var supports_desiretomeet = (MyOpenSpace) ? true : false;
Robustness - Check For Support • Remember, we don't want to write code like this: if (MyOpenSpace) { ...} else if (hi5) { ...} else { ...} • Apps should query by capability, not container name
Robustness - Check For Support • A workaround is to ignore the enums and go for string representations: var env = opensocial.getEnvironment();var supports_presence = env.supportsField( opensocial.Environment.ObjectType.PERSON, "presence");var supports_desiretomeet = env.supportsField( opensocial.Environment.ObjectType.PERSON,"DESIRE_TO_MEET");var supports_lookingfor = env.supportsField( opensocial.Environment.ObjectType.PERSON,"lookingFor");
Robustness - Check For Support Wait a sec, isn’t this a worst practice?
Robustness - Check For Support • Thankfully, the specification addresses this issue: “Extra person, activity or other object fields should be defined in an enum under the container's namespace, and the environment should allow applications to discover these fields.”
Robustness - Check For Support • Thankfully, the specification addresses this issue: “For example, if the fieldorkut.PersonField.SPECIAL_FIELDis defined as‘orkut.specialPersonField’, then opensocial.getEnvironment().supportsField( "person", "orkut.specialPersonField") opensocial.getEnvironment().supportsField( opensocial.Environment.ObjectType.PERSON, orkut.PersonField.SPECIAL_FIELD) and should both return true.”* *http://rurl.org/qr4
Robustness - Check For Support • We now have runtime access to capabilities across all containers: • Drawback: Now we have to be sensitive to the underlying string value for Profile field enums
Robustness - Check For Support • Use: • Now, instead of: if (MyOpenSpace) { ...} else if (hi5) { ...} else { ...} if (supports_presence) { ...} if (supports_desiretomeet) { ...} if (supports_lookingfor) { ...}
Container-Specific Extensions Hair Extensions Leg Extensions Tax Extensions
Extensions - Discovery • OpenSocial is powerful because containers can add their own functionality. • Add support for photos, videos, new profile fields, custom features, developer tools, etc... • OpenSocial is weak because too many custom extensions leads to code like this: if (MySpace) { ...} else if (hi5) { ...} else { ... } • With code like this, why have a standard API in the first place?
Extensions - Discovery • Extensions don’t need to be an all-or-nothing thing. For example, the following code posts an activity with a MediaItem to orkut: var opts = [], items = []; var mediaItem = opensocial.newActivityMediaItem( opensocial.Activity.MediaItem.Type.IMAGE, "http://imageurl"); items.push(mediaItem);opts[opensocial.Activity.Field.MEDIA_ITEMS] = items;var activity = opensocial.newActivity(opts);opensocial.requestCreateActivity( activity,opensocial.CreateActivityPriority.HIGH);
Extensions - Discovery • While the standard OpenSocial specification does not support adding links to these entries, hi5 has implemented an additional call that can attach a link to a media item: mediaItem.setField( hi5.ActivityMediaItemField.LINK, "http://linkurl"); • This call depends on the optional hi5 gadget feature. • You can check for this feature like this: gadgets.util.hasFeature('hi5')
Extensions - Discovery • Now this code posts the same MediaItem to orkut, but also adds a link when running in hi5: var opts = [], items = []; var mediaItem = opensocial.newActivityMediaItem( opensocial.Activity.MediaItem.Type.IMAGE, "http://imageurl"); if (gadgets.util.hasFeature('hi5')) { mediaItem.setField( hi5.ActivityMediaItemField.LINK, "http://linkurl"); } items.push(mediaItem);opts[opensocial.Activity.Field.MEDIA_ITEMS] = items;var activity = opensocial.newActivity(opts);opensocial.requestCreateActivity( activity,opensocial.CreateActivityPriority.HIGH);
Extensions - Optional Features • Some features are "nice to have" but aren't always required for core functionality • Example: hi5 allows the definition of “callback” URLs that get pinged whenever a user adds or removes your application. • As a developer, you may find callbacks good for metrics, but not necessary to run the application. • Instead of using <Requires feature=”x”>, OpenSocial allows you to include a feature if it exists on the container using <Optional feature=”x”>.
Extensions - Optional Features • This code pings your server upon add and remove events if the container supports the hi5-lifecycle feature, but continues to work even if the container does not: <?xml version="1.0" encoding="UTF-8" ?><Module> <ModulePrefs title="Optional features are fun"> <Require feature="opensocial-0.7" /> <Optional feature="hi5-lifecycle"> <Param name="installPingUrl" value="http://myserver/install"/> <Param name="removePingUrl" value="http://myserver/uninstall"/> </Optional> </ModulePrefs> ... </Module>
Extensions - Optional Features • Optional features are a great way for the platform to grow. • Containers are free to innovate and move ahead of the platform, and good ideas are rolled back into the spec. • Example: Lifecycle events are a new OpenSocial feature in version 0.8
Extensions - Common Objects to Extend • DataRequests function request() { var request = container.newDataRequest(); var photoReq = MyOpenSpace.DataRequest. newFetchPhotosRequest( opensocial.DataRequest.PersonId.VIEWER); request.add(photoReq, 'photos'); request.send(response);};
Extensions - Common Objects to Extend • Fields var opt_params = {};opt_params[ opensocial.DataRequest .PeopleRequestFields.PROFILE_DETAILS] = [ MyOpenSpace.Person.Field.MOOD, MyOpenSpace.Person.Field.HEADLINE, MyOpenSpace.Person.Field.DESIRE_TO_MEET ];
Resources • API Reference • orkut & iGoogle • http://code.google.com/apis/opensocial/docs/0.7/reference/http://rurl.org/qra • MySpace • http://developer.myspace.com/community/myspace/myopenspace.aspxhttp://rurl.org/qrb • hi5 • http://www.hi5networks.com/platform/wiki/JsAPIhttp://rurl.org/qrc