Hub 1.1 Proposal Nov 2008

From MemberWiki

Jump to: navigation, search

2009-01-04: Isn't it a typo below where it says OpenAjax.hub.Hub.prototype.disconnect instead of OpenAjax.hub.ManagedHub.prototype.disconnect?

/**
 * Error
 * 
 * Standard Error names used when the standard functions need to throw Errors.
 */
OpenAjax.hub.Error = {
	// An invalid argument, including a params object that is invalid, was provided:
	BadParameters: "OpenAjax.hub.BadParameters",
	// The specified hub has been disconnected and cannot perform the requested
	// operation:
	Disconnected: "OpenAjax.hub.Disconnected",
	// Container with specified ID already exists:
	Duplicate: "OpenAjax.hub.Duplicate",
	// The specified ManagedHub has no such Container (or it has been removed)
	NoContainer: "OpenAjax.hub.NoContainer",
	// The specified ManagedHub or Container has no such subscription
	NoSubscription: "OpenAjax.hub.NoSubscription",
	// Permission denied by manager's security policy
	NotAllowed: "OpenAjax.hub.NotAllowed"
};

////////////////////////////////////////////////////////////////////////////////

/**
 * SecurityAlert
 * 
 * Standard codes used when attempted security violations are detected. Unlike
 * Errors, these codes are not thrown as exceptions but rather passed into the 
 * SecurityAlertHandler function registered with the Hub instance.
 */
OpenAjax.hub.SecurityAlert = {
	// Container did not load (possible frame phishing attack)
	LoadTimeout: "OpenAjax.hub.LoadTimeout",
	// Hub suspects a frame phishing attack against the specified container
	FramePhish: "OpenAjax.hub.FramePhish",
	// Hub detected a message forgery that purports to come to a specifed
	// container
	ForgedMsg: "OpenAjax.hub.ForgedMsg"
};

////////////////////////////////////////////////////////////////////////////////

/**
 * Hub interface
 * 
 * Hub is implemented on the manager side by ManagedHub and on the client side by ClientHub.
 */

/**
 * Subscribe to the specified topic.
 *
 * @param {String} topic
 * @param {Function} onComplete   invoked to inform on success/failure
 * @param {Function} onData   invoked when an event is published on the given 'topic'
 * @param {Object} scope (see below)
 * @param {Object} subscriberData  Information that subscriber wants returned to it
 *     in the onData callback
 * 
 * The "scope" parameter is the scope for both onData and onComplete. When either of
 * these functions is called, any references to the JavaScript keyword "this" will refer to
 * the scope object.
 * 
 * @return subscription handle
 * @type {OpenAjax.hub.SubscriptionHandle}
 * 
 * @throws {OpenAjax.hub.Error.Disconnected}	if this.isConnected() returns false
 * @throws {OpenAjax.hub.Error.BadParameter}	if one of the parameters is invalid
 */
OpenAjax.hub.Hub.prototype.subscribe = function( topic, onComplete, onData, scope, subscriberData ) {}

/**
 * Publish an event on 'topic' with the given data.
 *
 * @param {String} topic
 * @param {*} data
 * 
 * @return 'true' on success, 'false' otherwise
 * @type {boolean}
 * 
 * @throws {OpenAjax.hub.Error.Disconnected}	if this.isConnected() returns false
 * @throws {OpenAjax.hub.Error.BadParameter}	if one of the parameters is invalid
 */
OpenAjax.hub.Hub.prototype.publish = function( topic, data ) {}

/**
 * Unsubscribe from the topic represented by the 'subscriptionHandle'
 *
 * @param {OpenAjax.hub.SubscriptionHandle} subscriptionHandle
 * @param {function} onComplete
 * @param {Object} scope
 *
 * @throws {OpenAjax.hub.Error.NoSubscription}	if there is no such subscription
 */
OpenAjax.hub.Hub.prototype.unsubscribe = function( subscriptionHandle, onComplete, scope ) {}

/**
 * Returns true if disconnect() has NOT been called on this Hub
 * 
 * @return Boolean
 * @type {Boolean}
 * 
 * @throws {OpenAjax.hub.Error.Disconnected}	if this.isConnected() returns false
 */
OpenAjax.hub.Hub.prototype.isConnected = function() {}

/**
 * Returns this Hub's params object
 * 
 * @return parameters
 * @type {Object}
 */
OpenAjax.hub.Hub.prototype.getParameters = function() {}

/**
 * Tests whether this object is equal to obj
 * 
 * @param {Object} obj
 * @return	Boolean
 * @type {Boolean}
 */
OpenAjax.hub.Hub.prototype.equals = function(obj) {}

/**
 * safeCall and safeDelete
 * 
 * If a function that destroys a subscription, hub or container is called
 * from a callback of that subscription, hub or container, it is important to 
 * defer deletion of the object while it is still in use on the stack.
 * 
 * These functions are provided for use by Container and HubClient 
 * implementations as well as for the private use of the ManagedHub 
 * implementation itself.
 */

/**
 * Call a callback function. If we are in the middle of a callback function,
 * cleanup operations must behave differently than they would otherwise, so
 * hub and container implementations must make all calls to application callback 
 * functions via this function, thus allowing us to track when we are in a callback
 * and when we are not.
 * 
 * @param {Object} scope
 * @param {Object} func
 * @param {Object} args
 */
OpenAjax.hub.Hub.safeCall = function(scope, func, args) {}

/**
 * If in the middle of a callback, defer deletion until we are no longer in
 * a callback. If not, then if obj exists, immediately delete obj and all
 * other objects whose deletion was deferred previously.
 * Otherwise, if obj does not exist, immediately delete all
 * other objects whose deletion was deferred previously.
 * 
 * @param {Object} obj
 */
OpenAjax.hub.Hub.safeDelete = function(obj) {} 

////////////////////////////////////////////////////////////////////////////////

/**
 * Container
 * 
 * Container represents an instance of a manager-side object that contains and communicates 
 * with a single client. The container might be an inline container, an iframe FIM container,
 * an iframe PostMessage container or another implementation.
 */

/**
 * NOTE: replaces 'OpenAjax.hub.configureProvider()',
 *                'providerObject.configureProvider()',
 *                'OpenAjax.hubspi.registerClient()',
 *                'connHandle.bind()'
 */
/**
 * @param {Object} hub
 * @param {Object} clientID
 * @param {Object} parameters
 * 		onConnect: function(container)
 * 		onDisconnect: function(container)
 * 		onSecurityAlert: function(container, securityAlert)
 * 		onError: function(container, error)
 */
OpenAjax.hub.Container = function( hub, clientID, parameters ) {}

/**
 * Send a message to the client inside this container. This function must only
 * be called by ManagedHub. 
 * 
 * @param {String} topic
 * @param {*} data
 * @param {String} subscriptionId
 */
OpenAjax.hub.Container.prototype.sendEvent = function(topic, data, subscriptionId) {}

/**
 * Shut down and remove this container. This function is only
 * be called by ManagedHub. 
 */
OpenAjax.hub.Container.prototype.remove = function() {}

/**
 * Returns true if the given client is connected to the managed hub.
 *
 * @return true if the client is connected to the managed hub
 * @type boolean
 */
OpenAjax.hub.Container.prototype.isConnected = function() {}

OpenAjax.hub.Container.prototype.getHub = function() {}
OpenAjax.hub.Container.prototype.getClientID = function() {}
OpenAjax.hub.Container.prototype.getParameters = function() {}
OpenAjax.hub.Container.prototype.equals = function(obj) {}

////////////////////////////////////////////////////////////////////////////////

/**
 * ManagedHub interface
 * 
 * Extends Hub interface
 */

/**
 * Create a new managed-hub instance.
 * 
 * @param {Object} params
 * 
 * 	Once params is passed into this function, it belongs exclusively
 * 	to the ManagedHub and MUST NOT be modified or destroyed by manager 
 * 	application code. 
 * 
 * 	The params object may contain the following properties:
 * 
 * @param params.onPublish: {function}
 * 	Called whenever a container tries to publish. REQUIRED.
 * 	If not present, BadParameters Error is thrown.
 * @param params.onSubscribe: {function}
 * 	Called whenever a container tries to subscribe. REQUIRED.
 * 	If not present, BadParameters Error is thrown.
 * @param params.onSecurityAlertError: {function}
 * 	Called when an attempted security breach is thwarted
 *	If no value is specified, default is NO REPORTING.
 * @param params.onWarning: {function}
 * 	Called when a non-threatening error is detected
 * @param params.scope: {Object}
 * 	Whenever one of the ManagedHub's callback functions is called,
 * 	references to "this" in the callback will refer to the scope object
 * 	If no scope is provided, default is window.
 */
OpenAjax.hub.ManagedHub = function( params ) {}

/**
 * The client represented by the 'container' object is subscribing to the given
 * 'topic'.  This method is called by the container implementation.
 * 
 * @param {Object} container
 * @param {String} topic
 * @param {String} subscriptionID
 *
 * @return subscription handle
 * @type {OpenAjax.hub.SubscriptionHandle}
 * 
 * @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
 * @throws {OpenAjax.hub.Error.NotAllowed} if subscription request is denied by 
 * 		onSubscribe security policy
 * @throws {OpenAjax.hub.Error.BadParameter} if one of the parameters is invalid
 */
OpenAjax.hub.ManagedHub.prototype.subscribeForClient = function( container, topic, subscriptionID ) {}

/**
 * The client represented by the 'container' object is unsubscribing from the
 * 'topic' identified by the 'subscriptionID'.  This method is called by the
 * container implementation.
 *
 * NOTE: replaces 'OpenAjax.hubspi.unsubscribeReq()'
 *
 * @param {Object} container
 * @param {OpenAjax.hub.SubscriptionHandle} subscriptionHandle
 */
OpenAjax.hub.ManagedHub.prototype.unsubscribeForClient = function( container, subscriptionHandle ) {}

/**
 * The client represented by the 'container' object is unsubscribing from the
 * 'topic' identified by the 'subscriptionID'.  This method is called by the
 * container implementation.
 *
 * @param {Object} container
 * @param {OpenAjax.hub.SubscriptionHandle} subscriptionHandle
 * 
 * @throws {OpenAjax.hub.Error.NoSubscription}	if subscriptionHandle does not refer 
 * 			to a valid subscription
 */
OpenAjax.hub.ManagedHub.prototype.publishForClient = function( container, topic, data ) {}

/**
 * Destroy this ManagedHub
 * 
 * 1. Sets state to DISCONNECTED. All subsequent attempts to add containers,
 * 		publish or subscribe will throw the Disconnected error. We will
 * 		continue to allow "cleanup" operations such as removeContainer
 * 		and unsubscribe, as well as read-only operations such as 
 * 		isConnected, equals and getParameters
 * 2. Remove all Containers. Actual deletion of Container objects is immediate 
 * 		if we are not currently in a callback; otherwise it is deferred until 
 * 		after all callbacks return.
 * 3. Safely destroy this ManagedHub. Actual deletion of this object is immediate 
 * 		if we are not currently in a callback; otherwise it is deferred until 
 * 		after all callbacks return.
 */
OpenAjax.hub.Hub.prototype.disconnect = function() {}

/**
 * Get a container belonging to this ManagedHub by its clientID, or null
 * if this ManagedHub has no such container
 * 
 * @param {Object} containerId
 */
OpenAjax.hub.ManagedHub.prototype.getContainer = function( containerId ) {}

/**
 * List all containers belonging to this ManagedHub
 * 
 * @return array
 * @type {Array}
 */
OpenAjax.hub.ManagedHub.prototype.listContainers = function() {}

/**
 * Add a container to this ManagedHub
 * 
 * @param {Object} container
 * 
 * @throws {OpenAjax.hub.Error.Duplicate} if there is already a Container
 * 	in this ManagedHub whose clientId is the same as that of container
 * @throws {OpenAjax.hub.Error.Disconnected}	if this.isConnected() returns false
 */
OpenAjax.hub.ManagedHub.prototype.addContainer = function( container ) {}

/**
 * Remove a container from this ManagedHub immediately
 * 
 * 1. Shuts down the container by calling container.remove()
 * 2. Removes container from the hub
 * 3. Deletes container
 * 
 * @param {Object} containerId
 * 
 * @throws {OpenAjax.hub.Error.NoContainer} if no such container is found
 * @throws {OpenAjax.hub.Error.Disconnected}	if this.isConnected() returns false
 */
OpenAjax.hub.ManagedHub.prototype.removeContainer = function( containerId ) {}

// Implementation must implement Hub interface as well

////////////////////////////////////////////////////////////////////////////////

/**
 * HubClient interface 
 * 
 * Extends Hub interface.
 * 
 * Each HubClient implementation is specific to a particular implementation 
 * of Container.
 */

/**
 * Constructor for HubClient. All HubClient constructors must have this signature.
 * 
 * @param params Parameters used to instantiate the HubClient
 *    Once "params" is passed into this constructor, it belongs exclusively
 *    to this HubClient and MUST NOT be modified by client application
 *    code. 
 * 
 * The params object may contain the following properties:
 * 
 * @param params.onSecurityAlertError: {function}
 *    Called when an attempted security breach is thwarted
 *    If no value is specified, default is NO REPORTING.
 * @param params.onWarning: {function}
 *    Called when a non-threatening error is detected
 * @param params.scope: {Object}
 *    Whenever one of the HubClient's callback functions is called,
 *    references to "this" in the callback will refer to the scope object
 *    If no scope is provided, default is window.
 */
OpenAjax.hub.HubClient = function( params ) {}

/**
 * Connect to the parent ManagedHub
 * 
 * @param {Function} onComplete
 *   Scope object for the onComplete callback is theHubClient.getParameters().scope
 */
OpenAjax.hub.HubClient.prototype.connectToParent = function( onComplete ) {}

/**
 * Disconnect from the hub.
 * 
 * @param {Function} onComplete
 *   Scope object for the onComplete callback is theHubClient.getParameters().scope
 */
OpenAjax.hub.HubClient.prototype.disconnect = function( onComplete ) {}


/**
 * Implementation must also implement Hub interface
 */

Architecture diagrams

In the diagrams below, the colors of the boxes specify which boxes are implemented by app builders, which are implemented by the Hub itself, and which are implemented by one or more Container vendors.

The asterisks refer to cardinality. Things without Asterisks are singletons within their window. I should have included an asterisk on Subscription (typo).

OpenAjax.hub.Subscription is a matter of some thought at the moment. This will either be an opaque subscription handle or a Dumb Old Data Object with operations like getHub, getTopic, getScope, getOnData, getOnComplete, getSubscriberData, isSubscribed, and equals.

OAH11_Modules.png

In the diagram below, the horizontal yellow arrows in the center represents proprietary, implementation-specific communication between a Container and its associated HubClient. There is no standard for such communication.

The orange vertical arrows represent the public, OAH-standard APIs that allow general-purpose hub implementations to interact with various implementations of Container and HubClient.

The purplish horizontal arrows toward the sides represent the public, OAH-standard APIs that manager and client application code uses to interact with the hub.

OAH11_Interactions.png

Other notes

> > Jon: On a tangent issue, should client even find out about being denied
> > permission? My feeling is yes. There is an argument that any information to
> > malicious components is bad, but in this case, either we are 
> sandboxing or we
> > aren't. OK to tell components they were denied, but don't provide 
> any details.
> 
> On subscribe, this is easy and makes a lot of sense.
> 
> On publish, it turns out that this is a lot more problematic. There are 2
> serious issues.
> 
> 1. The onPublish callback does not actually prevent a publish. It
>     prevents individual subscribers from receiving the published
>     data. So a single publish() may be both successful and
>     unsuccessful. 
> 
>     Possible resolution: NotAllowed indicates that perfectly
>     legitimate subscribers (which were allowed to subscribe to
>     topic T by onSubscribe) were prevented by onPublish from
>     receiving a message published on topic T.
> 
>     If we adopt this approach, then publishForClient throws
>     NotAllowed if onPublish returns false for even one of the
>     subscriptions, and HubClient notifies the client code of
>     the failure.
> 
> 2. If the HubClient notifies the client application that a
>     publish attempt was NotAllowed, then the client
>     application probably needs to determine which publish
>     failed. Is the information contained in the warning
>     sufficient to identify the culprit?
> 
>     Some possible approaches:
> 
>     a. Hub.publish should return a handle or token that can
>         be provided in the onWarning callback, to associate
>         the callback with a specific publish.
>     b. NAK returns topic and data that were published, and
>         the onWarning callback includes topic and data in
>         this case.
>     c. Provide an onComplete callback for publish.
>     d. Punt. Don't try to match callbacks to publishes. Client
>         just gets a generic error saying that some publish was
>         NotAllowed.
>     e. Eliminate all notification of NotAllowed for publish
>     f. Eliminate all notification of NotAllowed for subscribe
>         as well as for publish.

Decision: (e) in more detail, the functionality at the hub level is as
follows: 
 
    When onSubscribe denies a client's permission to subscribe
    to a topic, the Container communicates the rejection to the
    HubClient. The HubClient notifies the client application
    via the onComplete callback, specifying the error
    (OpenAjax.hub.Error.NotAllowed) and identifying the
    HubClient subscription handle associated with the
    rejection.

    When onPublish prevents a data item published by a client
    from being delivered to a subscriber, THE PUBLISHING
    CONTAINER IS NEVER NOTIFIED. When a client publishes, it
    cannot tell how many subscribers exist, and it cannot tell
    how many of the existing subscribers received the published
    data item.