// The Client object is how people should access the Helix Web Services server
// via AJAX calls (that uses jQuery).
//
// The API here is promise-oriented, which makes it slightly different to work
// with than the Ruby or Qt SDKs.
// Initialize jQuery that can be tested on node.js or run in a browser.
var $ = require('jquery');
// When executing tests, this needs to be used to initialize jquery, which...
// is wonderfully automated.
//if (typeof(process) == 'undefined') {
// $ = require('jquery');
//} else {
// $ = require('jquery')(require("jsdom").jsdom().parentWindow);
//
// var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
//
// $.support.cors = true;
// $.ajaxSettings.xhr = function () {
// return new XMLHttpRequest();
// };
//}
//if (typeof(btoa) == 'undefined') {
// btoa = require('btoa');
//}
var assign = require('object-assign');
require('./polyfill');
// Construct a new HelixWebServicesClient interface
/**
* Create the main client object used to communicate via remote calls.
*
* @param {Object} options Set various properties on this object.
* @constructor
*/
function HelixWebServicesClient(options) {
options = options || {};
/**
* The base URL, e.g. `http://example.com` of the web server.
*
* @type {string}
*/
this.url = options.url;
/**
* The Perforce user name.
*
* @type {string}
*/
this.user = options.user;
/**
* If accessing an HWS instance behind a proxy, it's probably available
* underneath a subdirectory
*
* @type {string}
*/
this.prefix = options.prefix || "";
/**
* Set a security token if you know it. This is used for authenticated
* methods.
*
* @type {string}
*/
this.session_token = options.session_token;
this._expiredCallbacks = [];
}
// Use our main constructor as a namespace for exporting models.
HelixWebServicesClient.Models = require('./models');
//-------------------------------------------------------------------------
// Local (private) Methods
//-------------------------------------------------------------------------
// Private method that will call jQuery.ajax and return it's promise.
//
// Options:
// - client
// - method
// - url
// - ... and others, passed to jQuery.ajax directly
function authAjax(options) {
var ajaxOpts = {
beforeSend: function (xhr) {
var auth = btoa(options.client.user + ":" + options.client.session_token);
xhr.setRequestHeader("Authorization", "Basic " + auth);
}
};
// Other options are just included into ajaxOpts
copy = assign({}, options);
delete copy.client;
assign(ajaxOpts, copy);
var ajax = $.ajax(ajaxOpts);
// We check for session expiration failures and trigger a sepcial handler on
// our client.
var deferred = $.Deferred();
ajax.done(deferred.resolve);
ajax.fail(function(jqXHR, textStatus, error) {
if (jqXHR.status == 403) {
options.client._expiredCallbacks.forEach(function(cb) { cb.call(); });
} else {
deferred.reject(jqXHR, textStatus, error);
}
});
return deferred.promise();
}
// Will alter the path, encoding each subpath component along the route,
// and encode it to become a path parameter.
function normalizePath(path) {
if (!path) {
return '';
}
if (path.startsWith('//')) {
path = path.substring(2);
}
parts = path.split('/');
return parts.map(function(p) { return encodeURIComponent(p); }).join('/');
}
assign(HelixWebServicesClient.prototype, {
//-------------------------------------------------------------------------
// Callbacks
//-------------------------------------------------------------------------
/**
* When your session expires on any particular call, this callback will get
* triggered.
*
* @param {function} callback A function() {} that gets called back if the
* session is no longer valid.
*/
addSessionExpiredHandler: function(callback) {
this._expiredCallbacks.push(callback);
},
//-------------------------------------------------------------------------
// Asynchronous Methods
//-------------------------------------------------------------------------
/**
* Creates a new session token by logging into the server.
*
* @param {String} [user] Optional attribute. If you specify it, we'll cache it as the
* `user` property.
* @param {String} password Required Perforce password for the user.
* @returns {Promise.<String>|Promise.<jqXHR, String, error>} On success,
* you'll receive the security token, which will be cached on this client
* object as the `session_token` property.
* @memberOf! HelixWebServicesClient#
*/
logIn: function (user, password) {
if (!password) {
password = user;
user = this.user;
}
// TODO we very likely want to configure ajax for common error handling
var ajax = $.post(
this.urlTo('/auth/v1/sessions'),
{user: this.user, password: password}
);
// We wrap the promise in order to cache the session before done() is
// invoked.
var deferred = $.Deferred();
var self = this;
ajax.done(function (data, textStatus, jqXHR) {
self.session_token = data;
deferred.resolve(self.session_token);
});
ajax.fail(function (jqXHR, textStatus, error) {
deferred.reject(jqXHR, textStatus, error);
});
return deferred.promise();
},
/**
* Destroys the session (on the server)
*
* @returns {Promise|Promise<jqXHR, textStatus, error>}
* @memberOf! HelixWebServicesClient#
*/
logOut: function () {
var ajax = authAjax({
client: this,
method: 'DELETE',
url: this.urlTo('/auth/v1/sessions/' + this.session_token)
});
var deferred = $.Deferred();
ajax.done(function () {
deferred.resolve();
});
ajax.fail(deferred.reject);
return deferred.promise();
},
/**
* List files at the particular directory level.
*
* @param {String} path The directory to list. Should be an absolute depot
* path, e.g., `//depot/dirA`. When empty, this lists
* depots in the system.
*
* @returns {Promise.<Array.<PathItem>>|Promise.<jqXHR, String, error>}
* Promises resolve to a list of PathItem models or the error data. Each
* PathItem is either all depot paths (if `path` is empty) or the child
* Directory or Files of `path`.
*
* @memberOf! HelixWebServicesClient#
*/
listFiles: function (path) {
var subPath = normalizePath(path);
var ajax = authAjax({
client: this,
method: 'GET',
url: this.urlTo('/perforce/v1/files/' + subPath),
dataType: 'json'
});
var deferred = $.Deferred();
ajax.done(function (items) {
var arr = HelixWebServicesClient.Models.PathItem.fromArray(items);
deferred.resolve(arr);
});
ajax.fail(deferred.reject);
return deferred.promise();
},
/**
* Create a new Helix Sync project.
*
* @param {Object|Project} project
* @returns {Promise.<Project>|Promise.<jqXHR, String, error>} Will return
* the 'updated' project structure, which likely has several default
* configuration values set.
* @memberOf! HelixWebServicesClient#
*/
createSyncProject: function (project) {
var ajax = authAjax({
client: this,
method: 'POST',
url: this.urlTo('/sync/v1/projects'),
data: JSON.stringify(project),
contentType: 'application/json',
dataType: 'json',
processData: false
});
var deferred = $.Deferred();
ajax.done(function (updated) {
deferred.resolve(updated);
});
ajax.fail(deferred.reject);
return deferred.promise();
},
/**
* List the Helix Sync projects the current user is a member of.
*
* @returns {*|Promise.<Array.<Project>>|Promise.<jqXHR, textStatus, error>}
* @memberOf! HelixWebServicesClient#
*/
listMySyncProjects: function () {
return this.syncProjects({members: this.user});
},
/**
* List all Helix Sync projects on the server.
*
* @returns {*|Promise.<Array.<Project>>|Promise.<jqXHR, textStatus, error>}
* @memberOf! HelixWebServicesClient#
*/
listAllSyncProjects: function () {
return this.syncProjects();
},
// TODO this should resolve to an array of our project models instead of an array-like Object
/**
* Fetch a listing of Helix Sync projects from the server.
*
* @param {Object} [options] Set 'listType'
* @returns {Promise.<Array<Project>>|Promise<jqXHR, textStatus, error>}
* @memberOf! HelixWebServicesClient#
*/
syncProjects: function (options) {
var ajaxOptions = {
client: this,
method: 'GET',
url: this.urlTo('/sync/v1/projects'),
dataType: 'json'
};
if (options)
ajaxOptions['data'] = options;
var ajax = authAjax(ajaxOptions);
var deferred = $.Deferred();
ajax.done(function (projects) {
deferred.resolve(projects);
});
ajax.fail(deferred.reject);
return deferred.promise();
},
//-------------------------------------------------------------------------
// Helper Methods
//-------------------------------------------------------------------------
// These methods shouldn't return Promise interfaces.
urlTo: function (path) {
var url = this.url || "";
if (this.prefix) {
url += this.prefix;
}
url += path;
return url;
}
});
module.exports = HelixWebServicesClient;