Skip to content
This repository has been archived by the owner on Jul 24, 2019. It is now read-only.

Commit

Permalink
initial yuidoc docs
Browse files Browse the repository at this point in the history
  • Loading branch information
gojko committed Nov 26, 2014
1 parent 0137950 commit 7946ce6
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 4 deletions.
32 changes: 32 additions & 0 deletions public/lib/activity-log.js
@@ -1,8 +1,23 @@
/*global jQuery, MM, observable*/
/**
* Utility logging class that can dispatch events. Used by other classes
* as a central tracking and analytics mechanism. Caches a list of most
* recent events in memory for troubleshooting purposes.
*
* @class ActivityLog
* @constructor
* @param {int} maxNumberOfElements the maximum number of elements to keep in memory
*/
MM.ActivityLog = function (maxNumberOfElements) {
'use strict';
var activityLog = [], nextId = 1, self = this;
observable(this);
/**
* Tracks an event and dispatches the event to all observers.
*
* @method log
* @param {String} ...args a list of arguments to log. By convention, the first argument is a category, the second is an action, the others are arbitrary strings
*/
this.log = function () {
var analyticArgs = ['log'];
if (activityLog.length === maxNumberOfElements) {
Expand All @@ -23,11 +38,28 @@ MM.ActivityLog = function (maxNumberOfElements) {
});
self.dispatchEvent.apply(self, analyticArgs);
};
/**
* Shorthand error logging method, it will call log with an Error category and dispatch a separate error event
* @method error
*/
this.error = function (message) {
self.log('Error', message);
self.dispatchEvent('error', message, activityLog);
};
/**
* Utility method to look at the list of most recent events
*
* @method getLog
* @return the list of most recent events
*/
this.getLog = activityLog.slice.bind(activityLog);
/**
* Starts an asynchronous timer - can be stopped at a later point.
* @method timer
* @param {String} category the category to log
* @param {String} action the action to log
* @return javascript object with an **end** method, which will stop the timer and log the total number of milliseconds taken since start
*/
this.timer = function (category, action) {
var start = Date.now();
return {
Expand Down
87 changes: 86 additions & 1 deletion public/lib/gold-api.js
@@ -1,4 +1,63 @@
/* global MM, jQuery, FormData, _ */
/**
* MM Gold API wrapper. This class is a JavaScript interface to the remote HTTP Gold server API,
* and provides low-level methods for authentication and generating security tokens.
* It implements the _configurationGenerator_ interface required by the {{#crossLink "LayoutExportController"}}{{/crossLink}}
* so it can be used directly to construct an export workflow class.
*
* ## Access licenses
*
* MindMup Gold requires a valid license for most file operations. The license is effectively a secret key
* identifying the user, and granting access to the server resources for storage and export. The license
* is used for billing purposes to associate the resource usage with an active Gold subscription.
*
* There are two ways to allow users to access the service:
*
* 1. Allow your users to log in with their individual Gold accounts, effectively using their subscriptions
* 2. Use a single license for all the users
*
* For the first scenario, each user session should go through the Authentication Workflow described below. For
* the second scenario, it is better to execute the authentication once manually, and store the license
* key securely on a server. The license key never expires and should be kept secret.
*
* To make this class more useful, the actual storage and management of the license is abstracted into a separate
* interface, so third party implementers can provide their own storage mechanism. See
* the {{#crossLink "GoldLicenseManager"}}{{/crossLink}} for more information.
*
* ## Authentication workflow
*
* MindMup Gold does not use passwords - instead, the authentication workflow
* is similar to the typical password reset scenario - a one-time
* authentication token can be requested from the server, and the token is sent
* to the e-mail associated with the account. This token can then be used to
* retrieve the Gold license key (in effect, logging in). See
* {{#crossLink "GoldApi/requestCode:method"}}{{/crossLink}} and
* {{#crossLink "GoldApi/restoreLicenseWithCode:method"}}{{/crossLink}}
* for more information.
*
* For extra security, the internal HTTP API requires the sender to provide a
* token known only to the requester while asking for a code, and supply the
* same token again when retrieving the license. This effectively protects
* against the e-mail being intercepted. A third party reading e-mails with
* access codes will not be able to use them, because they don't know the
* client token. The JavaScript API hides this complexity and automatically
* generates a random string to send. This limits the execution of the two
* calls to a single instance of GoldApi, as the current string is stored in
* memory.
*
* The one-time codes sent by mail have to be used within a 10 minute time span
* to retrieve a license, and only one such code can be active at any given
* time. Requesting a new code effectively cancels the previous one. (The
* license string itself never expires automatically, and can be cached
* locally).
*
* @class GoldApi
* @constructor
* @param {GoldLicenseManager} goldLicenseManager an object implementing the GoldLicenseManager API
* @param {String} goldApiUrl the end-point for the HTTP API
* @param {ActivityLog} activityLog activity log instance for logging purposes
* @param {String} goldBucketName the S3 bucket name for public and anonymous files
*/
MM.GoldApi = function (goldLicenseManager, goldApiUrl, activityLog, goldBucketName) {
'use strict';
var self = this,
Expand Down Expand Up @@ -76,6 +135,15 @@ MM.GoldApi = function (goldLicenseManager, goldApiUrl, activityLog, goldBucketNa
var license = goldLicenseManager.getLicense();
return self.exec('license/cancel_subscription', {'license': JSON.stringify(license)});
};
/**
* Creates an export configuration for server-side exports. See
* {{#crossLink "LayoutExportController/startExport:method"}}{{/crossLink}}
* for an example of how to use it.
*
* @method generateExportConfiguration
* @param {String} format one of supported formats
* @return {jQuery.Deferred} asynchronous promise that will be resolved with the export configuration
*/
self.generateExportConfiguration = function (format) {
var license = goldLicenseManager.getLicense();
return self.exec('file/export_config', {'license': JSON.stringify(license), 'format': format});
Expand All @@ -84,11 +152,28 @@ MM.GoldApi = function (goldLicenseManager, goldApiUrl, activityLog, goldBucketNa
var license = goldLicenseManager.getLicense();
return self.exec('file/echo_config', {'license': JSON.stringify(license), 'format': format, 'contenttype': contentType});
};
/**
* Request a one-time password from the Gold server. This method starts the remote authentication
* workflow, and will result in a one-time password being sent to the e-mail address associated with the account.
*
* @method requestCode
* @param {String} identifier email or account name
* @param {String} [clientToken]
* @return {jQuery.Deferred} an asynchronous promise that will resolve if the e-mail was sent from the server and reject in case of an error
*/
self.requestCode = function (identifier) {
currentOnetimePassword = MM.onetimePassword();
currentIdentifier = identifier;
return self.exec('license/request_code', {'identifier': identifier, 'one_time_pw': currentOnetimePassword});
};
/**
* Load the license manager with the license, using a one time password sent by the Gold server. This
* method completes the remote authentication worksflow.
*
* @method restoreLicenseWithCode
* @param {String} code the one-time password received after requesting the code
* @return {jQuery.Deferred} an asynchronous promise that will resolve or reject depending on the outcome. if successful, the GoldLicenseManager will have its license set.
*/
self.restoreLicenseWithCode = function (code) {
var deferred = jQuery.Deferred();
if (currentOnetimePassword && currentIdentifier) {
Expand Down Expand Up @@ -158,4 +243,4 @@ MM.onetimePassword = function () {
};

return s4() + '-' + s4();
};
};
49 changes: 49 additions & 0 deletions public/lib/gold-license-manager.js
@@ -1,4 +1,17 @@
/* global MM, observable, jQuery, _ */
/**
* Utility method to manage the active Gold license in memory. Uses a browser storage to cache the license
* and expects a visual widget to listen to observable events to handle possible authentication requests.
*
* The class is split out
* from the {{#crossLink "GoldApi"}}{{/crossLink}} class so third-party users can provide an alternative
* implementation that reads a license from disk or something similar.
*
* @class GoldLicenseManager
* @constructor
* @param {JsonStorage} storage an object store for license persistence
* @param {String} storageKey the hash-key used to store the license in the storage
*/
MM.GoldLicenseManager = function (storage, storageKey) {
'use strict';
var self = this,
Expand All @@ -7,9 +20,22 @@ MM.GoldLicenseManager = function (storage, storageKey) {
return license && license.accountType === 'mindmup-gold';
};
observable(this);
/**
* Get the current license from memory, without trying to asynchronously retrieve it from network
*
* @method getLicense
* @return {Object} the current license from storage
*/
this.getLicense = function () {
return storage.getItem(storageKey);
};
/**
* Asynchronous method which will try to get a local license, and if not available notify any observers to
* show the UI for logging in or retrieving the license over network in some other way
* @method retrieveLicense
* @param {Boolean} forceAuthentication if true, force authentication even if logged in (eg to force a login or replacing an expired license)
* @return {jQuery.Deferred} a promise that will be resolved when a license is finally set or rejected
*/
this.retrieveLicense = function (forceAuthentication) {
currentDeferred = undefined;
if (!forceAuthentication && this.getLicense()) {
Expand All @@ -19,6 +45,13 @@ MM.GoldLicenseManager = function (storage, storageKey) {
self.dispatchEvent('license-entry-required');
return currentDeferred.promise();
};
/**
* Set the in-memory cached license
*
* @method storeLicense
* @param {String or JSON} licenseArg gold license
* @return true if the license is in correct format and storage accepted it, false otherwise
*/
this.storeLicense = function (licenseArg) {
var license = licenseArg;
if (_.isString(licenseArg)) {
Expand All @@ -37,13 +70,29 @@ MM.GoldLicenseManager = function (storage, storageKey) {
this.removeLicense = function () {
storage.setItem(storageKey, undefined);
};
/**
* Stop the current asynchronous license entry process, notifying all observers about failure.
*
* _This is an optional method, and you only need to re-implement it if you want to re-use the MindMup Gold License entry widget._
*
*
* @method cancelLicenseEntry
*/
this.cancelLicenseEntry = function () {
var deferred = currentDeferred;
if (currentDeferred) {
currentDeferred = undefined;
deferred.reject('user-cancel');
}
};
/**
* Complete the current asynchronous license entry, notifying all observers about successful completion. this implementation
* expects that storeLicense was already called.
*
* _This is an optional method, and you only need to re-implement it if you want to re-use the MindMup Gold License entry widget._
*
* @method completeLicenseEntry
*/
this.completeLicenseEntry = function () {
var deferred = currentDeferred;
if (currentDeferred) {
Expand Down
30 changes: 29 additions & 1 deletion public/lib/json-storage.js
@@ -1,17 +1,45 @@
/*global _, observable, jQuery, MM*/
/*global MM*/
/**
* A simple wrapper that allows objects to be stored as JSON strings in a HTML5 storage. It
* automatically applies JSON.stringify and JSON.parse when storing and retrieving objects
*
* @class JsonStorage
* @constructor
* @param {Object} storage object implementing the following API (for example a HTML5 localStorage)
* @param {function} storage.setItem function(String key, String value)
* @param {function} storage.getItem function(String key)
* @param {function} storage.removeItem function(String key)
*/
MM.JsonStorage = function (storage) {
'use strict';
var self = this;
/**
* Store an object under a key
* @method setItem
* @param {String} key the storage key
* @param {Object} value an object to be stored, has to be JSON serializable
*/
self.setItem = function (key, value) {
return storage.setItem(key, JSON.stringify(value));
};
/**
* Get an item from storage
* @method getItem
* @param {String} key the storage key used to save the object
* @return {Object} a JSON-parsed object from storage
*/
self.getItem = function (key) {
var item = storage.getItem(key);
try {
return JSON.parse(item);
} catch (e) {
}
};
/**
* Remove an object from storage
* @method remove
* @param {String} key the storage key used to save the object
*/
self.remove = function (key) {
storage.removeItem(key);
};
Expand Down
40 changes: 39 additions & 1 deletion public/lib/layout-export.js
@@ -1,4 +1,37 @@
/*global jQuery, MM, _ */
/**
* Utility class that implements the workflow for requesting an export and polling for results.
*
* ## Export workflow
*
* MindMup.com supports several server processes that convert map (or layout) files into other formats (images, slides etc).
* These server side resources require a valid Gold license for storage and billing, so the access is controlled
* using the {{#crossLink "GoldApi"}}{{/crossLink}}. The general workflow to order an export is:
*
* 1. Ask the Gold API for an upload token for a particular upload format. Currently supported formats are:
* * pdf - the map file as a scalable vector PDF
* * png - the map as a bitmap image (PNG)
* * presentation.pdf - the slideshow as a scalable vector PDF
* * presentation.pptx - the slideshow as a PowerPoint file
* The Gold API will provide all information required to upload a
* file to Amazon S3, as well as signed URLs to check for the conversion result or error
* 2. Upload the source content to Amazon S3. Note that some formats require a layout, some require an entire map.
* 3. Poll the result and error URLs periodically. If the file appears on the result URL, download it and send to users. If
* a file appears on the error URL or nothing appears until the polling timeout, fail and stop polling
*
* This class coordinates all the complexity of the workflow and conversions in a simple convenience method.
* For an example of how to wire it up, see https://github.com/mindmup/mindmup/blob/master/public/main.js
*
* @class LayoutExportController
* @constructor
* @param {Object} exportFunctions a hash-map _format -> function_ that coverts the active map to the content actually uploaded to the server
* @param {Object} configurationGenerator object implementing the following API (for example a {{#crossLink "GoldApi"}}{{/crossLink}} instance)
* @param {function} configurationGenerator.generateExportConfiguration (String format)
* @param {Object} storageApi object implementing the following API (for example a {{#crossLink "S3Api"}}{{/crossLink}} instance):
* @param {function} storageApi.save (String content, Object configuration, Object properties)
* @param {function} storageApi.poll (URL urlToPoll, Object options)
* @param {ActivityLog} activityLog logging interface
*/
MM.LayoutExportController = function (exportFunctions, configurationGenerator, storageApi, activityLog) {
'use strict';
var self = this,
Expand All @@ -9,7 +42,12 @@ MM.LayoutExportController = function (exportFunctions, configurationGenerator, s
}
return format.toUpperCase() + ' Export';
};

/**
* @method startExport
* @param {String} format one of the supported formats, provided in the constructor
* @param [exportProperties] any additional properties that will be merged into the exported data
* @return {jQuery.Deferred} a jQuery promise that will be resolved with the URL of the exported document if successful
*/
self.startExport = function (format, exportProperties) {
var deferred = jQuery.Deferred(),
eventType = getEventType(format),
Expand Down
28 changes: 27 additions & 1 deletion public/lib/s3-api.js
@@ -1,8 +1,25 @@
/*global jQuery, MM, FormData, window, _*/

/**
*
* Utility class that implements AWS S3 POST upload interface and
* understands AWS S3 listing responses
*
* @class S3Api
* @constructor
*/
MM.S3Api = function () {
'use strict';
var self = this;
/**
* Upload a file to S3 using the AWS S3 Post mechanism
* @method save
* @param {String} contentToSave file content to upload
* @param {Object} saveConfiguration a hash containing
* @param {String} saveConfiguration.key AWS S3 bucket key to upload
* @param {String} saveConfiguration.AWSAccessKeyId AWS S3 access key ID of the requesting user
* @param {String} saveConfiguration.policy AWS S3 POST upload policy, base64 encoded
* @param {String} saveConfiguration.signature AWS S3 POST signed policy
*/
this.save = function (contentToSave, saveConfiguration, options) {
var formData = new FormData(),
savePolicy = options && options.isPrivate ? 'bucket-owner-read' : 'public-read',
Expand Down Expand Up @@ -47,6 +64,15 @@ MM.S3Api = function () {
return deferred.promise();
};
self.pollerDefaults = {sleepPeriod: 1000, timeoutPeriod: 120000};
/**
* Poll until a file becomes available on AWS S3
* @method poll
* @param {String} signedListUrl a signed AWS S3 URL for listing on a key prefix
* @param {Object} [opts] additional options
* @param {int} [opts.sleepPeriod] sleep period in milliseconds between each poll (default=1 sec)
* @param {int} [opts.timeoutPeriod] maximum total time before polling operation fails (default = 12 secs)
* @param {function} [opts.stoppedSemaphore] a predicate function that is checked to see if polling should be aborted
*/
self.poll = function (signedListUrl, options) {
var sleepTimeoutId,
timeoutId,
Expand Down

0 comments on commit 7946ce6

Please sign in to comment.