Client/server publish/subscribe, presence and key/value storage for AppEngine
A profile is a collection of clients that all speak as the same user, and so should appear as the same to subscribers. Profiles are identified by a string key assigned by the server. Clients can fetch their own profile by calling getProfile() or currentProfile(). Profile information for other users is provided in the sender field of a Message.
A client keeps its profile when it logs in to a Google account, accept if that account is already associated with a different profile. In that case, messages that were sent using the old profile are re-assigned to the profile associated with the logged in user.
Logging out changes profiles.
The profile key is passed to Google Analytics (if enabled) as the userId, so sessions from different clients across different browsers are associated with the same user for analytics purposes.
A client is a persistent identifier for a given browser. It is identified by a string key generated by the client and stored in localStorage, so it is persistent across restarts (except in incognito mode or the like). A client is associated with exactly one profile at any time, but may change associated profiles on log in.
Logging out generates and stores a new client key.
An instance is a persistent identifier for a single connection to the server. It is generated when a new Cosmopolite instance is constructed or whenever the server -> client channel connection is interrupted.
Instances are used to track Subscriptions and Pins which cease to exist (from the server perspective) when a client disconnects.
A channel is the message push connection from the server to the client used to send updates. It uses Google AppEngine's Channel service.
In addition to message passing, the channel library also provides notification of connection state to the server. This is passed to the application code using the onConnect and onDisconnect callbacks.
A subject is a topic that clients publish and subscribe to. Subjects may be strings, numbers or objects with access control information embedded. The follow are all valid and different subjects:
"foo"5{ name: "foo", readable_only_by: "XXXprofileXXX" }{ name: "foo", writable_only_by: "YYYprofileYYY" }{ name: "foo", readable_only_by: "XXXprofileXXX", writable_only_by: "YYYprofileYYY" }{ name: "foo", readable_only_by: "admin" }{ name: "foo", writable_only_by: "admin" }{ name: "foo", readable_only_by: "me" }{ name: "foo", writable_only_by: "me" }admin is a magic value that
only allows accounts marked as administrators in the AppEngine application to perform the action.
me is s placeholder that is replaced on the server side with the current profile ID
in both requests and responses. Using the result of currentProfile()
instead of me will fetch the same data from the server but appear as a different
group in the client.
A subscription ties an instance to a subject and requests that the client be kept informed of changes to that subject. Changes that cause notification are:
Subscriptions are automatically destroyed on the server side when an instance disconnects. The client library keeps lists of subjects and the last messages seen from them and automatically re-subscribes after reconnection. This process is transparent to application code.
Subscriptions are created by the application using the subscribe() method.
A message is a piece of information sent by a client to a subject. It is stored by the server and transmitted to any clients currently subscribing to that subject.
A message can consist of any type that is serializable in JSON: numbers, booleans, strings, null, arrays and objects composed of these values (or of other arrays and objects). Serialization and deserialization is handled by Cosmopolite.
When a message is received (via the onMessage callback or the return result from getMessages() or getLastMessage()), metadata is attached, packaged into an object with the message data itself:
{
// Server-assigned sequence number
"id": 1,
// Type may also be "pin"
"event_type": "message",
// Sender-assigned UUID
"sender_message_id": "cbdae48d-7a02-4850-208c-986b18121b9c",
// Canonical subject that message was sent to
"subject": {
"name": "foobar"
},
// Server-recorded UNIX timestamp that the server received the message
"created": 1402212355.0,
// Deserialized message data
"message": "test",
// Profile key of sender
"sender": "XXXprofileXXX"
}
Messages are sent by the application using the sendMessage() method.
A pin is a message that is deleted when the instance that sent it disconnects. Subscribers to the pin's subject are notified (via the onPin and onUnpin callbacks) when it is added and deleted. This allows pins to be used for presence detection.
Pins are added and deleted by the application using the pin() and unpin() methods.
Arguments: none
The onConnect callback is called when the channel is established to the server. It is paired with the onDisconnect callback except when shutdown() is called.
Arguments: none
The onDisconnect callback is called when the channel is disconnected. This can happen due to connectivity problems, server outages or periodically when the channel expires.
Arguments:
username: The user's Google username string.logout_url: A URL that the user can load to terminate their session. This should
be opened in a new window (using target="_blank").The onLogin callback is called when the user logs in. It is also called after the first RPC to the server succeeds if the user was already logged in. It is guaranteed that either onLogin or onLogout is called after the first RPC succeeds.
Arguments:
login_url: A URL that the user cna load to log in to their Google account. This
should be opened in a new window (using target="_blank").The onLogin callback is called when the user logs out or is logged out by Google. It is also called after the first RPC to the server succeeds if the user was not already logged in. It is guaranteed that either onLogin or onLogout is called after the first RPC succeeds.
Arguments:
message: A message object including metadata and message
content.The onMessage callback is called when a historical or new message is received from the server. It is only called for subjects that the client is currently subscribed to.
After a call to subscribe(), onMessage fires once for each historical message received from the server. After all historical messages have been processed, the resolve callback for the Promise returned by subscribe() is called. Any onMessage events after that point are for new messages.
Calling subscribe() more than once may lead to additional historical messages being retreived. onMessage will not fire twice for the same message unless unsubscribe() is called in the interim.
onMessage fires for messages sent by our own profile and client. You can filter messages by comparing their sender field to the return value of currentProfile() to avoid processing your own messages.
onMessage may fire when we are not connected (per the onConnect and onDisconnect callbacks), as messages may be received in RPC responses instead of over the channel
Arguments:
message: A message object including metadata and message
content.The onPin callback is called when a new pin is received from the server. It is only called for subjects that the client is currently subscribed to.
After a call to subscribe(), onPin fires once for each pin already present in the subject. After all standing pins have been processed, the resolve callback for the Promise returned by subscribe() is called. Any onPin events after that point are for new pins.
onPin callbacks are paired with onUnpin callbacks except when shutdown() is called.
onPin fires for pins from own profile and client. You can filter pins by comparing their sender field to the return value of currentProfile() to avoid processing your own pins.
onPin may fire when we are not connected (per the onConnect and onDisconnect callbacks), as messages may be received in RPC responses instead of over the channel
Arguments:
message: A message object including metadata and message
content.The onUnpin callback is called when a pin is deleted on from the server. It is also called when the channel disconnects from the server, hence the server state of pins is unknown.
onUnpin callbacks are paired with onPin callbacks except when shutdown() is called.
onUnpin fires for pins from own profile and client. You can filter pins by comparing their sender field to the return value of currentProfile() to avoid processing your own pins.
onUnpin may fire when we are not connected (per the onConnect and onDisconnect callbacks), as messages may be received in RPC responses instead of over the channel
Arguments:
callbacks (optional): An object with callback names as
keys and functions as values. All callbacks are optional.urlPrefix (optional): A string containing the URL path at which to find the
server-side cosmopolite endpoints. Defaults to "/cosmopolite". See the
architecture diagram.namespace (optional): A string containing a namespace prefix to be used for
entries in localStorage. Defaults to "cosmopolite". Changing this allows multiple local instances
of Cosmopolite to co-exist on the same domain without interfering.trackingID (optional): A string containing a
Google Analytics tracking /
web property ID (usually starting with "UA-") identifying your analytics account. Passing
this argument enables event tracking and the trackEvent() method.Construct a new Cosmopolite instance. Always use the new keyword. All methods on
the instance are available for immediate use, though some deferred initialization is occuring and
some actions may be queued.
Arguments: none
Start shutdown of this instance. No callbacks will be fired after this call. Some RPCs may be outstanding and some cleanup may be deferred.
Arguments: none
Returns: A boolean indicating whether we currently believe that we are
connected to the server.
Note that this information may lag by the detection delay built into the channel API.
Arguments: none
Returns: A Promise to provide a profile ID string.
As the profile ID is generated by the server, it requires a round trip to complete successfully before it is known to the client. getProfile() returns a promise that resolves immediately if the profile ID is already known, or fires when it becomes available.
Arguments: none
Returns: A profile ID string, or null if unknown.
currentProfile() has simpler semantics than getProfile() but may return null if the initial RPC to the server is still outstanding. It is not safe to use just after construction. It is safe to use from callbacks and promises returned by other functions, as those require a server response.
Arguments:
subject: A valid subject (string, number or object with
specific keys), or an Array of subjectsmessages (optional): An integer number of historical messages to retrieve from
this subject if available. 0 means no messages; -1 means all messages.lastID (optional): The id field from the last message received by the client.
The server will send historical messages starting from the message after that.Returns: A
Promise that resolves with no arguments on success, or rejects with no arguments if the client
is denied access to read the subject. If the subject argument was an Array of
subjects, returns an Array of Promises that correspond.
subscribe() creates a subscription on the server to a particular subject. The subscription is tied to our current instance but is re-created by the client library when it reconnects, transparent to the application.
Before the promise returned by subscribe() resolves, the onMessage and onPin callbacks will fire for any historical messages requests and available and for any current pins. After the promise resolves, messages and pins sent to this subject will cause the callbacks to fire when they are received. Additionally, the onUnpin callback will fire for deleted pins.
Callbacks fire for messages sent by our own profile and client. You can filter messages by comparing their sender field to the return value of currentProfile() to avoid processing your own messages.
subscribe() may be called multiple times for the same subject to retrieve additional historical messages. The subscriptions do not stack, however; a single call to unsubscribe() undoes all preceding subscribe() calls to that subject.
subscribe() calls may fail (and reject the promise) when subscription is requested to a
subject that sets readable_only_by and that does not match our
profile.
Arguments:
subject: A valid subject (string, number or object with
specific keys)Returns: A Promise that resolves on RPC completion.
Unsubscribe from a subject and stop receiving callbacks related to it. Callbacks stop immediately after calling unsubscribe().
A single call to unsubscribe() undoes all calls to subscribe() for the given subject.
Arguments:
subject: A valid subject (string, number or object with
specific keys)message: The message data. Any value that JSON can
serialize is valid.Returns: A
Promise that resolves on success, or rejects with no arguments if the client is denied access
to write to the subject. On success, a message argument is passed to the
resolution callback; it differs from the original message argument in that it has
metadata added by the server.
Sends a message to the given subject. The message contents are serialized and sent to the server (see architecture). The server adds metadata (sequence number, timestamp, sender profile ID), stores the message and sends it on to any current subscribers to the subject, including possibly back to the local client. Future subscribers can fetch the historical message from the server's storage (as opposed to a pin which is ephemeral and tied to the current instance).
The message may be rejected by server (causing the promise to reject) if the
subject sets writable_only_by and it does not match our
current profile.
If we are also subscribed to the given subject and have provided an onMessage callback, it will fire for this message when it is received back from the server. You can filter messages by comparing their sender field to the return value of currentProfile() to avoid processing your own messages.
Arguments:
subject: A valid subject (string, number or object with
specific keys)Returns: An ordered (oldest to newest) array of message objects.
The client library keeps a cache of messages received from subscriptions. getMessage() returns the contents of that cache.
Note that only subjects for which subscribe() has been called are valid to pass to getMessage(), and that getMessage() only returns historical messages as specified by the arguments to subscribe(). To fetch more historical messages, call subscribe() again with different arguments.
Arguments:
subject: A valid subject (string, number or object with
specific keys)Returns: The most recent message object received, or null if none.
Similar to getMessages(), but only returns the most recent message received from the server (in server sequence number ordering). This is useful for applications using Cosmopolite as a key/value store that don't need to interact with the historical messages.
Arguments:
subject: A valid subject (string, number or object with
specific keys)message: The message data. Any value that JSON can
serialize is valid.Returns: A Promise that resolves on success, or rejects with no arguments if the client is denied access to write to the subject. If successful, it is passed a single string argument containing a unique identifier for the pin that can later be passed on unpin().
A pin is like a message, with some differences: it is tied to the lifetime of the current instance and channel, it can be deleted by the publishing client (by calling unpin(), and it is unordered on the server.
The pin may be rejected by server (causing the promise to reject) if the
subject sets writable_only_by and it does not match our
current profile.
If we are also subscribed to the given subject and have provided an onPin callback, it will fire for this pin when it is received back from the server. You can filter pins by comparing their sender field to the return value of currentProfile() to avoid processing your own pins.
Arguments:
id: An ID string previously passed to the resolve callback of the promise
returned by pin() or the sender_message_id from the
onPin callback or the return value of getPins().
Returns: A Promise that resolves with no arguments on RPC completion
Remove a currently active pin. The pin must have been added by this client, but not necessarily by this instance (the client library automatically re-adds pins after reconnection).
Arguments:
subject: A valid subject (string, number or object with
specific keys)Returns: An unordered array of message-like objects.
The client library keeps a list of currently active pins subscriptions. getPins() returns the contents of that cache.
Arguments: the same as the ga() function from Google Analytics.
If trackingID was passed to the Cosmopolite constructor, Cosmopolite sends analytics events on load and subscribe. To avoid duplicate work initializing the analytics library, trackEvent() provides a passthrough to the analytics interface.
Cosmopolite does not send a pageview event. To tie together tracking data, it is recommended that you send at least one pageview from your application code.
Arguments: none
Returns: A new, random universally unique identifier string
Generate and return a new UUID. This generates a fully random verison 4 UUID; it is not MAC-based.