c9-core/plugins/c9.vfs.client/endpoint.js

390 wiersze
13 KiB
JavaScript

define(function(require, exports, module) {
"use strict";
main.consumes = ["Plugin", "auth", "http", "api", "error_handler", "metrics"];
main.provides = ["vfs.endpoint"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var auth = imports.auth;
var http = imports.http;
var api = imports.api;
var errorHandler = imports.error_handler;
var metrics = imports.metrics;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var urlServers, lastVfs;
var query = require("url").parse(document.location.href, true).query;
if (query.vfs) {
if (!query.vfs.match(/^https:\/\/.*\/vfs$/))
alert("Bad VFS URL passed, expected: https://host/vfs");
urlServers = [{
url: query.vfs,
region: "url"
}];
}
if (query.vfs || query.region) {
var vfs = recallVfs();
if (vfs) {
if (query.vfs && query.vfs !== vfs.url)
deleteOldVfs();
else if (query.region && query.region !== vfs.region)
deleteOldVfs();
}
}
if (query.vfs)
options.updateServers = false;
var region = query.region || options.region;
var servers;
var pendingServerReqs = [];
initDefaultServers();
options.pid = options.pid || 1;
/***** Methods *****/
function initDefaultServers(baseURI) {
if (options.getServers)
return options.getServers(init);
init();
function init() {
options.getServers = undefined;
var loc = require("url").parse(baseURI || document.baseURI || window.location.href);
var defaultServers = [{
url: loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + "/vfs",
region: "default"
}];
servers = (urlServers || options.servers || defaultServers).map(function(server) {
server.url = server.url.replace(/\/*$/, "");
return server;
});
pendingServerReqs.forEach(function(cb) {
cb(null, servers);
});
}
}
function getServers(callback) {
if (typeof options.getServers == "function")
return pendingServerReqs.push(callback);
if (!options.updateServers)
return callback(null, servers);
// first time take the ones from the options
var _servers = servers;
if (_servers) {
servers = null;
return callback(null, _servers);
}
api.vfs.get("servers", function(err, servers) {
if (err) return callback(err);
return callback(null, servers.servers);
});
}
function getVfsEndpoint(version, callback) {
getServers(function(err, _servers) {
if (err) {
if (err.code !== "EDISCONNECT")
errorHandler.reportError(new Error("Could not get list of VFS servers"), { cause: err });
metrics.increment("vfs.failed.connect_getservers", 1, true);
initDefaultServers();
_servers = servers;
}
getVfsUrl(version, _servers, function(err, vfsid, url, region) {
if (err) return callback(err);
callback(null, {
url: url,
region: region,
home: vfsid + "/home",
project: vfsid + "/workspace",
socket: vfsid + "/socket",
ping: vfsid,
serviceUrl: vfsid,
});
});
});
}
function isOnline(callback) {
http.request("/_ping", {
timeout: 3000,
headers: {
Accept: "application/json"
}
}, function(err, data, res) {
callback(err, !err);
});
}
function isServerAlive(url, callback) {
auth.request(url, {
headers: {
Accept: "application/json"
}
}, function(err, data, res) {
if (err)
deleteOldVfs();
callback(err, !err);
});
}
function getVfsUrl(version, vfsServers, callback) {
var vfs = recallVfs();
if (vfs && vfs.vfsid) {
auth.request(vfs.vfsid, {
method: "GET",
headers: {
Accept: "application/json"
}
}, function(err, res) {
if (err) {
deleteOldVfs();
return getVfsUrl(version, vfsServers, callback);
}
callback(null, vfs.vfsid, vfs.url, vfs.region);
});
return;
}
var servers = shuffleServers(vfsServers);
// check for version
if (servers.length && !servers.filter(function(s) { return s.version !== version; }).length)
return onProtocolChange(callback);
// just take the first server that doesn't return an error
(function tryNext(i) {
if (i >= servers.length) {
metrics.increment("vfs.failed.connect_all", 1, true);
return callback(new Error("Disconnected: Could not reach your workspace. Please try again later."));
}
var server = servers[i];
auth.request(server.url + "/" + options.pid, {
method: "POST",
timeout: 120000,
body: {
version: version
},
headers: {
Accept: "application/json"
}
}, function(err, res) {
// the workspace is not configured correctly
if (err && res && res.error) {
if (err.code == 429) {
// rate limited
setTimeout(function() {
tryNext(i);
}, res.error.retryIn || 10000);
return;
}
else if (err.code == 412 && res.error && res.error.subtype == "protocol_mismatch") {
return onProtocolChange(callback);
}
else if (err.code == 412) {
callback(fatalError(res.error.message, "dashboard"));
return;
}
else if (err.code === 428 && res.error) {
emit("restore", {
projectState: res.error.projectState,
premium: res.error.premium,
progress: res.error.progress || {
progress: 0,
nextProgress: 0,
message: ""
}
});
setTimeout(function() {
tryNext(i);
}, res.error.retryIn || 10000);
return;
}
else if (err.code == 403) {
if (res.error.blocked)
callback(fatalError(res.error.message, "dashboard"));
// forbidden. User doesn't have access
// wait a while before trying again
setTimeout(function() {
tryNext(i);
}, 10000);
return;
}
}
if (err) {
setTimeout(function() {
tryNext(i+1);
}, 2000);
return;
}
var vfs = rememberVfs(server, res.vfsid);
callback(null, vfs.vfsid, server.url, server.region);
});
})(0);
}
function onProtocolChange(callback) {
// I'm keeping this vague because we don't want users to blame
// a "cloud9 update" for losing work
deleteOldVfs();
return callback(fatalError("Protocol change detected", "reload"));
}
function shuffleServers(servers) {
servers = servers.slice();
var isBeta = region == "beta";
servers = servers.filter(function(s) {
return isBeta || s.region !== "beta";
});
return servers.sort(function(a, b) {
if (a.region == b.region) {
if (a.load < b.load)
return -1;
else
return 1;
}
else if (a.region == region)
return -1;
else if (b.region == region)
return 1;
else
return 0;
});
}
function rememberVfs(server, vfsid) {
var vfs = {
url: server.url,
region: server.region,
pid: options.pid,
vfsid: server.url + "/" + options.pid + "/" + vfsid,
readonly: options.readonly
};
var data = JSON.stringify(vfs);
var oldData = lastVfs || window.sessionStorage.getItem("vfsid");
if (oldData && oldData !== data)
deleteOldVfs();
lastVfs = data;
return vfs;
}
function recallVfs() {
var vfs;
try {
vfs = JSON.parse(lastVfs || window.sessionStorage.getItem("vfsid"));
if (!lastVfs) {
window.sessionStorage.removeItem("vfsid");
lastVfs = JSON.stringify(vfs);
}
} catch (e) {}
if (!vfs)
return null;
if (vfs.pid !== options.pid || vfs.readonly != options.readonly) {
deleteOldVfs();
return null;
}
return vfs;
}
function deleteOldVfs() {
var vfs;
try {
vfs = JSON.parse(lastVfs || window.sessionStorage.getItem("vfsid"));
} catch (e) {}
window.sessionStorage.removeItem("vfsid");
lastVfs = null;
if (!vfs) return;
auth.request(vfs.vfsid, {
method: "DELETE",
headers: {
Accept: "application/json"
}
}, function(err) {
if (err) console.error(vfs.vfsid, "deleted", err);
});
}
function fatalError(msg, action) {
var err = new Error(msg);
err.fatal = true;
err.action = action || "reload";
return err;
}
function saveToSessionStorage() {
try {
window.sessionStorage.setItem("vfsid", lastVfs);
} catch(e) {
// could throw a quota exception
}
}
plugin.on("load", function() {
window.addEventListener("unload", saveToSessionStorage);
});
plugin.on("unload", function() {
window.removeEventListener("unload", saveToSessionStorage);
});
/***** Register and define API *****/
/**
**/
plugin.freezePublicAPI({
/**
* Returns the URLs for the home and project REST API and the socket
*/
get: getVfsEndpoint,
/**
* Checks if the client has a network connection
*/
isOnline: isOnline,
/**
*
*/
clearCache: deleteOldVfs,
/**
* Checks if the current VFS server is still alive
*/
isServerAlive: isServerAlive
});
register(null, {
"vfs.endpoint": plugin
});
}
});