From d80711700d2e420d34ac9044aa8ad94365e1afc0 Mon Sep 17 00:00:00 2001 From: c9bot Date: Tue, 10 Feb 2015 06:36:50 +0000 Subject: [PATCH 01/12] c9-auto-bump 3.0.825 --- README.md | 3 ++- package.json | 2 +- plugins/c9.error/raygun.connect.js | 5 ----- plugins/c9.ide.plugins/debug.js | 10 ++++------ plugins/c9.ide.plugins/loader.js | 8 +++----- plugins/c9.vfs.server/vfs.js | 13 +------------ 6 files changed, 11 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 95a2bf6f..0a8bb88f 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,11 @@ We have several documentation resources for you: SDK documentationhttp://cloud9-sdk.readme.io/v0.1/docs API documentationhttp://docs.c9.io/api User documentationhttp://docs.c9.io + User docs repohttps://github.com/c9/docs.c9.io Please joing the mailinglist to get support or give support to the growing community of plugin developers: -https://groups.google.com/forum/#!forum/cloud9-sdk +https://groups.google.com/forum/#!forum/cloud9-plugin-development #### Installation #### diff --git a/package.json b/package.json index ebb512b7..2719bccc 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "c9.ide.find": "#be3bca94b7", "c9.ide.find.infiles": "#462928475c", "c9.ide.find.replace": "#fe41fa768d", - "c9.ide.run.debug": "#9a05fadc55", + "c9.ide.run.debug": "#b734a2a47f", "c9.ide.ace.emmet": "#e5f1a92ac3", "c9.ide.ace.gotoline": "#4d1a93172c", "c9.ide.ace.keymaps": "#422e83553b", diff --git a/plugins/c9.error/raygun.connect.js b/plugins/c9.error/raygun.connect.js index 37848ec3..d2793f39 100644 --- a/plugins/c9.error/raygun.connect.js +++ b/plugins/c9.error/raygun.connect.js @@ -71,11 +71,6 @@ function plugin(options, imports, register) { email: req.user.email }; } - else if (req.session) { - customData.user = { - id: req.session.uid - }; - } raygunClient.send(err, customData, function() {}, { host: parsedUrl.hostname, diff --git a/plugins/c9.ide.plugins/debug.js b/plugins/c9.ide.plugins/debug.js index bf7dd44b..b251e6a7 100644 --- a/plugins/c9.ide.plugins/debug.js +++ b/plugins/c9.ide.plugins/debug.js @@ -139,12 +139,10 @@ define(function(require, exports, module) { watch("~/.c9/plugins/" + pluginPath); var cfg = options.plugins[path]; - var host = vfs.baseUrl + "/"; - var base = join(String(c9.projectId), - "plugins", auth.accessToken); - - cfg.packagePath = host + join(base, pluginPath.replace(/^plugins\//, "")); - cfg.staticPrefix = host + join(base, name); + cfg.packagePath = join(vfs.baseUrl, c9.projectId, "plugins", + auth.accessToken, pluginPath.replace(/^plugins\//, "")); + cfg.staticPrefix = join(vfs.baseUrl, c9.projectId, "plugins", + auth.accessToken, name); cfg.apikey = "0000000000000000000000000000="; config.push(cfg); diff --git a/plugins/c9.ide.plugins/loader.js b/plugins/c9.ide.plugins/loader.js index 45c5a41b..d6320a8c 100644 --- a/plugins/c9.ide.plugins/loader.js +++ b/plugins/c9.ide.plugins/loader.js @@ -66,11 +66,9 @@ define(function(require, exports, module) { names.push(name); var path = options.packagePath + ".js"; - var host = vfs.baseUrl + "/"; - var base = join(String(c9.projectId), "plugins", auth.accessToken); - - options.packagePath = host + join(base, path.replace(/^plugins\//, "")); - options.staticPrefix = host + join(base, name); + var base = join(vfs.baseUrl, c9.projectId, "plugins", auth.accessToken); + options.packagePath = join(base, path.replace(/^plugins\//, "")); + options.staticPrefix = join(base, name); if (!options.setup) { wait++; diff --git a/plugins/c9.vfs.server/vfs.js b/plugins/c9.vfs.server/vfs.js index d75a8adc..9562eef9 100644 --- a/plugins/c9.vfs.server/vfs.js +++ b/plugins/c9.vfs.server/vfs.js @@ -19,11 +19,9 @@ function Vfs(vfs, master, options) { this.vfs = vfs; this.master = master; this.debug = options.debug || false; - this.logger = options.logger || {log: function(){}}; this.readonly = options.readonly || false; this.public = options.public || false; this.vfsOptions = options.vfsOptions || {}; - this.pid = this.vfsOptions.pid; var extendToken = options.extendToken; this.homeDir = options.homeDir; @@ -169,12 +167,6 @@ Vfs.prototype._createEngine = function(vfs, options) { that.socket.disconnect(); that.socket = socket; - socket.on('close', function (reason, description) { - var logMetadata = {collab: options.collab, reason: reason, description: description, id: that.id, sid: socket.id, pid: that.pid}; - console.log("Socket closed", logMetadata); - logMetadata.message = "Socket closed"; - that.logger.log(logMetadata); - }); var transport = new smith.EngineIoTransport(socket, true); var worker = new VfsWorker(vfs); @@ -193,10 +185,7 @@ Vfs.prototype._createEngine = function(vfs, options) { } worker.on("disconnect", function() { - var logMetadata = {collab: options.collab, id: that.id, sid: socket.id, pid: that.pid}; - console.log("VFS socket disconnect:", logMetadata); - logMetadata.message = "VFS socket disconnect"; - that.logger.log(logMetadata); + console.log("VFS socket disconnect:", options.collab, that.id, socket.id); if (options.collab) { if (collabApi) return disposeCollabClient(); From e3aaf421424a187733b08d309b7e6ee35432c213 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Tue, 10 Feb 2015 20:20:05 +0000 Subject: [PATCH 02/12] Add loaded and connected handlers --- package.json | 2 +- plugins/c9.ide.editors/document.js | 3 + plugins/c9.login.client/bootstrap.js | 283 +++++++++++++++++++++++++++ plugins/c9.login/legacy.js | 123 ++++++++++++ plugins/c9.login/legacy_strategy.js | 73 +++++++ plugins/c9.login/login.js | 135 +++++++++++++ plugins/c9.login/strategy.js | 45 +++++ 7 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 plugins/c9.login.client/bootstrap.js create mode 100644 plugins/c9.login/legacy.js create mode 100644 plugins/c9.login/legacy_strategy.js create mode 100644 plugins/c9.login/login.js create mode 100644 plugins/c9.login/strategy.js diff --git a/package.json b/package.json index 2719bccc..9d575cc4 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "c9.ide.preview": "#3c4dded23f", "c9.ide.preview.browser": "#be197b0464", "c9.ide.preview.markdown": "#bf952685f6", - "c9.ide.pubsub": "#e6526a20f7", + "c9.ide.pubsub": "#c102844931", "c9.ide.readonly": "#f6f07bbe42", "c9.ide.recentfiles": "#7c099abf40", "c9.ide.remote": "#37773d905b", diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index 013a49f0..7dbd1ad8 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -165,6 +165,9 @@ define(function(require, module, exports) { function progress(options) { emit("progress", options); + if (options.complete) { + emit.sticky("complete"); + } } function clone() { diff --git a/plugins/c9.login.client/bootstrap.js b/plugins/c9.login.client/bootstrap.js new file mode 100644 index 00000000..9f62d313 --- /dev/null +++ b/plugins/c9.login.client/bootstrap.js @@ -0,0 +1,283 @@ +(function(global) { +"use strict"; + +var token = ""; + +var auth = global.auth = function(options) { + // can only be called once + global.auth = null; + + var onLoad = options.onLoad; + var preload = options.preload || noop; + var authorized = options.authorized || noop; + var background = options.background || noop; + + importCssString("html.fulliframe, body.fulliframe {\ + overflow: hidden;\ + margin: auto;\ + height: 100%;\ + width: 100%;\ + }"); + + function noop(callback) { callback(); } + + if (onLoad) { + auth.parallel([ + background, + auth.serial([ + auth.parallel([ + preload, + login + ]), + authorized, + ]) + ])(done); + } + + function login(callback, errback) { + var oauth = new Auth(options.clientId, options.authorizationUrl, options.loginHint); + + oauth.authorize(true, function(err, _token) { + if (err) + return iframeLogin(); + + token = _token.access_token; + callback(null, token); + }); + + function iframeLogin() { + errback && errback(); + oauth.authorize(false, function(err, _token) { + if (err) return callback(err); + token = _token.access_token; + callback(null, token); + }); + } + + return function cancel() { + oauth.cancel(); + }; + } + + function done(err) { + onLoad(err, token); + } + + return { + login: login + }; +}; + +function bindScript(script) { + if (typeof script == "function") + return script; + else + return loadScript.bind(null, script, token); +} + +auth.serial = function(list) { + return function(callback) { + serial(list.map(bindScript), callback); + }; +}; + +auth.parallel = function(list) { + return function(callback) { + parallel(list.map(bindScript), callback); + }; +}; + +function loadScript(path, token, callback) { + var head = document.head || document.getElementsByTagName("head")[0] || document.documentElement; + var s = document.createElement('script'); + + var and = path.indexOf("?") >= 0 ? "&" : "?"; + s.src = path + (token ? and + "access_token=" + encodeURIComponent(token) : ""); + head.appendChild(s); + + s.onload = s.onreadystatechange = function(_, isAbort) { + if (isAbort || !s.readyState || s.readyState == "loaded" || s.readyState == "complete") { + s = s.onload = s.onreadystatechange = null; + if (!isAbort) + callback(); + } + }; +} + +// copied from ace/lib/dom +function importCssString(cssText) { + var style; + + if (document.createStyleSheet) { + style = document.createStyleSheet(); + style.cssText = cssText; + } else { + style = document.createElementNS + ? document.createElementNS("http://www.w3.org/1999/xhtml", "style") + : document.createElement("style"); + + style.appendChild(document.createTextNode(cssText)); + + (document.head || document.getElementsByTagName("head")[0] || document.documentElement).appendChild(style); + } +} + +function serial(handlers, callback) { + (function loop(i) { + if (i >= handlers.length) return callback(); + + handlers[i](function(err) { + if (err) return callback(err); + + loop(i+1); + }); + })(0); +} + +function parallel(handlers, callback) { + var hadErr = false; + var count = 0; + handlers.forEach(function(handler) { + handler(function(err) { + if (hadErr) return; + if (err) { + hadErr = true; + return callback(err); + } + count += 1; + if (count == handlers.length) + return callback(); + }); + }); +} + +// install exactly one global listener +var listeners = {}; +window.addEventListener("message", function(e) { + var token = e.data.token; + if (token) { + for (var url in listeners) { + if (url.indexOf(e.origin) === 0) { + var callback = listeners[url][token.state]; + delete listeners[url][token.state]; + if (callback) callback(null, token); + + // make sure later listeners can't steal the token + e.data.token = null; + break; + } + } + } +}, true); + + +function Auth(clientId, authorizationUrl, loginHint) { + this.clientId = clientId; + this.authorizationUrl = authorizationUrl; + this.loginHint = loginHint; + listeners[authorizationUrl] = {}; +} + +Auth.prototype.authorize = function(immediate, callback) { + if (typeof immediate == "function") + return this.authorize({}, immediate); + + immediate = immediate || false; + + var that = this; + this.state = uid(15); + + var url = this.authorizationUrl + + "?response_type=postmessage" + + "&client_id=" + encodeURIComponent(this.clientId) + + "&state=" + encodeURIComponent(this.state) + + "&style=overlay"; + + if (this.loginHint) + url += "&login_hint=" + encodeURIComponent(this.loginHint || ""); + + if (immediate) + url += "&immediate=1"; + + var frame = this._createFrame(url, immediate); + var timeout = immediate ? 3000 : 0; + + if (timeout) { + var timer = setTimeout(function() { + that._unpoll(); + callback(new Error("Login timed out")); + }, timeout); + } + + this._removeFrame = function() { + clearTimeout(timer); + + frame.parentNode.removeChild(frame); + document.documentElement.className = document.documentElement.className.replace(/\bfulliframe\b/, ""); + document.body.className = document.body.className.replace(/\bfulliframe\b/, ""); + that._removeFrame = null; + }; + + this._poll(function(err, token) { + if (that._removeFrame) + that._removeFrame(); + + if (err) + return callback(err); + + if (token.error) { + err = new Error(token.error); + err.code = token.error_code; + return callback(err); + } + + that.token = token; + return callback(null, token); + }); +}; + +Auth.prototype.cancel = function() { + this._unpoll(); + if (this._removeFrame) + this._removeFrame(); +}; + +Auth.prototype._createFrame = function(url, hidden) { + var frame = document.createElement("iframe"); + frame.setAttribute("src", url); + frame.setAttribute("frameborder", "0"); + if (hidden) { + frame.style.width = "1000px"; + frame.style.height = "1000px"; + frame.style.left = "-10000px"; + } + else { + frame.style.width = "100%"; + frame.style.height = "100%"; + frame.style.zIndex = "300000"; + document.documentElement.className += " fulliframe"; + document.body.className += " fulliframe"; + } + frame.style.position = "absolute"; + document.body.appendChild(frame); + return frame; +}; + +Auth.prototype._poll = function(callback) { + listeners[this.authorizationUrl][this.state] = callback; +}; + +Auth.prototype._unpoll = function() { + delete listeners[this.authorizationUrl][this.state]; +}; + +function uid(length) { + var buf = new Uint8Array(new ArrayBuffer(length)); + (window.crypto || window.msCrypto).getRandomValues(buf); + + return btoa(Array.prototype.reduce.call(buf, function(s, c) { + return s + String.fromCharCode(c); + }, "")).slice(0, length); +} + +})(this); \ No newline at end of file diff --git a/plugins/c9.login/legacy.js b/plugins/c9.login/legacy.js new file mode 100644 index 00000000..aba436fe --- /dev/null +++ b/plugins/c9.login/legacy.js @@ -0,0 +1,123 @@ +"use strict"; + +var assert = require("assert"); +var url = require("url"); +var Cloud9LegayStrategy = require("./legacy_strategy"); +var cookieSignature = require("cookie-signature"); +var decrypt = require("c9/crypt").decrypt; +var login = require("connect-ensure-login"); + +plugin.consumes = [ + "db", + "passport", + "connect.redirect", + "connect.cookieparser", + "session-store" +]; +plugin.provides = ["c9.login"]; + +module.exports = plugin; + +function plugin(options, imports, register) { + assert(options.appId, "Option 'appId' is required"); + assert(options.ideBaseUrl, "Option 'ideBaseUrl' is required"); + assert(options.baseUrl, "Option 'baseUrl' is required"); + assert(options.ssoCookie, "Option 'ssoCookie' is required"); + assert(options.ssoSecret, "Option 'ssoSecret' is required"); + + var db = imports.db; + var passport = imports.passport; + var sessionStore = imports["session-store"]; + + // use the 'proxy' cookie to have federated logout + passport.useStart(function(req, res, next) { + var hash; + // anonymous login + if (!req.cookies || !(hash = req.cookies[options.ssoCookie])) + return done(); + + var encrypted = cookieSignature.unsign(hash, options.ssoSecret); + if (!encrypted) + return done(); + + var sessionId = decrypt(encrypted, options.ssoSecret); + + sessionStore.get(sessionId, function(err, session) { + if (err) return done(err); + done(null, session && session.uid); + }); + + function done(err, ssoUid) { + if (err) return next(err); + ssoUid = ssoUid || -1; + var session = req.session; + if (session && session.passport && session.passport.user && session.passport.user != ssoUid) { + return req.session.regenerate(function(err) { + if (err) return next(err); + + if (session.returnTo) + req.session.returnTo = session.returnTo; + + delete req.user; + next(); + }); + } + else { + if (!req.session.passport) + req.session.passport = {}; + + req.session.passport.user = ssoUid; + next(); + } + } + }); + + var cloud9Strategy = new Cloud9LegayStrategy({ + clientID: options.appId, + ideBaseUrl: options.ideBaseUrl, + callback: options.baseUrl + "/auth/c9l/callback", + db: db + }); + + passport.use(cloud9Strategy); + + passport.section.get("/c9l", passport.authenticate("c9l")); + passport.section.get("/c9l/callback", [ + passport.authenticate("c9l"), + function(req, res, next) { + var user = req.user; + + if (user) { + req.login(user, function(err) { + if (err) return next(err); + res.returnTo(req, "/"); + }); + } + else { + res.redirect("/auth/c9l"); + } + } + ]); + + register(null, { + "c9.login": { + ensureLoggedIn: function() { + return function(req, res, next) { + var redirect = options.baseUrl + "/_auth/c9l"; + var nonce = req.parsedUrl.query.__c9_preview_id__; + + if (nonce) { + redirect += "?nonce=" + encodeURIComponent(nonce); + delete req.parsedUrl.query.__c9_preview_id__; + delete req.parsedUrl.search; + req.originalUrl = url.format(req.parsedUrl); + } + + login.ensureLoggedIn({ + redirectTo: redirect + })(req, res, next); + }; + } + } + }); +} \ No newline at end of file diff --git a/plugins/c9.login/legacy_strategy.js b/plugins/c9.login/legacy_strategy.js new file mode 100644 index 00000000..bdc78f30 --- /dev/null +++ b/plugins/c9.login/legacy_strategy.js @@ -0,0 +1,73 @@ +var passport = require('passport'); +var util = require('util'); +var InternalOAuthError = require("passport-oauth").InternalOAuthError; + + +function Cloud9Legacy(options) { + passport.Strategy.call(this); + this.name = 'c9l'; + + this.clientID = options.clientID; + this.ideBaseUrl = options.ideBaseUrl; + this.callback = options.callback; + this.db = options.db; +} + +/** + * Inherit from `passport.Strategy`. + */ +util.inherits(Cloud9Legacy, passport.Strategy); + +/** + * Authenticate request based on the contents of a HTTP Basic authorization + * header. + * + * @param {Object} req + * @api protected + */ +Cloud9Legacy.prototype.authenticate = function(req, options) { + var that = this; + options = options || {}; + + // the callback handler + if (req.query && req.query.code) { + this.db.AccessToken + .findOne({ + token: req.query.code + }) + .populate("user") + .exec(function(err, token) { + if (err) + return that.error(new InternalOAuthError('failed to obtain access token', err)); + + req.session.token = req.query.code; + that.success(token.user); + }); + return; + } + + var nonce = req.parsedUrl.query.nonce; + if (nonce) { + this.redirect( + this.ideBaseUrl + + "/api/nc/auth" + + "?response_type=nonce" + + "&client_id=" + encodeURIComponent(this.clientID + "_nonce") + + "&nonce=" + encodeURIComponent(nonce) + ); + } + else { + this.redirect( + this.ideBaseUrl + + "/api/nc/auth" + + "?response_type=token" + + "&client_id=" + encodeURIComponent(this.clientID) + + "&login_hint=" + encodeURIComponent(options.loginHint || "") + ); + } +}; + +/** + * Expose `Cloud9Legacy`. + */ +module.exports = Cloud9Legacy; \ No newline at end of file diff --git a/plugins/c9.login/login.js b/plugins/c9.login/login.js new file mode 100644 index 00000000..45d67244 --- /dev/null +++ b/plugins/c9.login/login.js @@ -0,0 +1,135 @@ +"use strict"; + +var assert = require("assert"); +var frontdoor = require("frontdoor"); +var cookie = require("cookie"); +var Passport = require("passport").Passport; +var Cloud9Strategy = require("./strategy"); + +plugin.consumes = [ + "session", + "connect.redirect" +]; +plugin.provides = ["c9.login"]; + +module.exports = plugin; + +function plugin(options, imports, register) { + assert(options.appId, "Option 'appId' is required"); + assert(options.appSecret, "Option 'appSecret' is required"); + assert(options.callback, "Option 'callback' is required"); + assert(options.logout, "Option 'logout' is required"); + assert(options.baseUrl, "Option 'baseUrl' is required"); + assert(options.domain, "Option 'domain' is required"); + assert(options.ssoCookie, "Option 'ssoCookie' is required"); + assert(options.ssoCookie.name, "Option 'ssoCookie.name' is required"); + assert(options.ssoCookie.maxAge, "Option 'ssoCookie.maxAge' is required"); + + var session = imports.session; + var passport = new Passport(); + + session.use(passport.initialize()); + session.use(function(req, res, next) { + passport.session()(req, res, function(err) { + if (err) return next(err); + if (!req.user) return next(); + + var uid = req.cookies[options.ssoCookie.name]; + if (uid != req.user.id) { + req.logout(); + return next(); + } + + next(); + }); + }); + + passport.serializeUser(function(user, done) { + var id; + try { + id = JSON.stringify(user); + } + catch (e) { + return done(e); + } + done(null, id); + }); + + passport.deserializeUser(function(id, done) { + var user; + try { + user = JSON.parse(id); + } + catch (e) { + return done(e); + } + done(null, user); + }); + + var cloud9Strategy = new Cloud9Strategy({ + clientID: options.appId, + clientSecret: options.appSecret, + callbackURL: options.callback, + userProfileURL: options.userProfileURL, + baseUrl: options.baseUrl, + }, function(accessToken, refreshToken, params, profile, done) { + var user = { + id: profile.id, + username: profile.username, + fullname: profile.displayName ? profile.displayName.trim() : profile.username, + token: accessToken + }; + done(null, user); + }); + + passport.use(cloud9Strategy); + + var api = frontdoor(); + passport.section = api.section("auth"); + session.use(api); + + passport.section.get("/logout", function(req, res, next) { + res.redirect(options.baseUrl + "/logout?redirect_uri=" + encodeURIComponent(options.logout)); + }); + passport.section.get("/cloud9", passport.authenticate("cloud9")); + passport.section.get("/cloud9/callback", function(req, res, next) { + passport.authenticate("cloud9", function(err, user, info) { + if (err) return next(err); + + if (user) { + req.login(user, function(err) { + if (err) return next(err); + setCookie(res, req.user.id, options.ssoCookie.maxAge); + res.returnTo(req, "/"); + }); + } + else { + res.redirect("/auth/cloud9"); + } + + })(req, res, next); + }); + + passport.section.get("/cloud9/logout", function(req, res, next) { + req.logout(); + clearCookie(res); + res.redirect("/"); + }); + + function clearCookie(res) { + setCookie(res, "", new Date(1)); + } + function setCookie(res, value, ttl) { + res.setHeader("Set-Cookie", cookie.serialize(options.ssoCookie.name, value, { + domain: "." + options.domain, + path: "/", + expires: ttl instanceof Date ? ttl : new Date(Date.now() + ttl), + secure: true, + httpOnly: true + })); + } + + register(null, { + "c9.login": passport + }); +} \ No newline at end of file diff --git a/plugins/c9.login/strategy.js b/plugins/c9.login/strategy.js new file mode 100644 index 00000000..3443d5e4 --- /dev/null +++ b/plugins/c9.login/strategy.js @@ -0,0 +1,45 @@ +var util = require('util'); +var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; +var InternalOAuthError = require('passport-oauth').InternalOAuthError; + +function Strategy(options, verify) { + options = options || {}; + var baseUrl = options.baseUrl || "https://auth.c9.io/oauth"; + + options.authorizationURL = baseUrl + "/authorize"; + options.tokenURL = baseUrl + "/access_token"; + options.scopeSeparator = ","; + + OAuth2Strategy.call(this, options, verify); + this.name = "cloud9"; + this._userProfileURL = options.userProfileURL || "https://api.c9.io/user"; +} + +util.inherits(Strategy, OAuth2Strategy); + +Strategy.prototype.userProfile = function(accessToken, done) { + this._oauth2.useAuthorizationHeaderforGET(true); + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { + if (err) + return done(new InternalOAuthError('failed to fetch user profile', err)); + + try { + var json = JSON.parse(body); + + var profile = { provider: "cloud9" }; + profile.id = json.id; + profile.displayName = json.name; + profile.username = json.login; + profile.emails = [{ value: json.email }]; + + profile._raw = body; + profile._json = json; + + done(null, profile); + } catch (e) { + done(e); + } + }); +}; + +module.exports = Strategy; \ No newline at end of file From 86dc6c8fd343e7cd4aa193da3b750eff89fcb936 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 13:41:56 +0000 Subject: [PATCH 03/12] Add docs --- plugins/c9.ide.editors/document.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index 7dbd1ad8..d565f4e8 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -166,7 +166,7 @@ define(function(require, module, exports) { function progress(options) { emit("progress", options); if (options.complete) { - emit.sticky("complete"); + emit.sticky("complete", options); } } @@ -487,6 +487,16 @@ define(function(require, module, exports) { * @param {Boolean} e.upload whether this is an upload (instead of a download). **/ "progress", + /** + * Fires when the initial document loaded completely + * @event once + * @param {Object} e + * @param {Number} e.loaded the number of bytes that have been downloaded/uploaded. + * @param {Number} e.total the total number of bytes for this file. + * @param {Boolean} e.complete whether the download has completed. + * @param {Boolean} e.upload whether this is an upload (instead of a download). + **/ + "complete", /** * Fires when this document is cloned * @event cone From b57880d281d88e6f05f4836c7d2638d19292a4ad Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 13:43:07 +0000 Subject: [PATCH 04/12] Simplify listening to ready events --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d575cc4..5af3097e 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "c9.ide.preview": "#3c4dded23f", "c9.ide.preview.browser": "#be197b0464", "c9.ide.preview.markdown": "#bf952685f6", - "c9.ide.pubsub": "#c102844931", + "c9.ide.pubsub": "#7dd0a37571", "c9.ide.readonly": "#f6f07bbe42", "c9.ide.recentfiles": "#7c099abf40", "c9.ide.remote": "#37773d905b", From b70b7a0938a51b6d875ed50c271c4c020fb24260 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 16:13:13 +0000 Subject: [PATCH 05/12] Set ready in tabManager --- plugins/c9.ide.editors/document.js | 22 +++++++++------------- plugins/c9.ide.editors/tabmanager.js | 3 ++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index d565f4e8..320226f8 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -165,9 +165,6 @@ define(function(require, module, exports) { function progress(options) { emit("progress", options); - if (options.complete) { - emit.sticky("complete", options); - } } function clone() { @@ -335,6 +332,15 @@ define(function(require, module, exports) { editor = v; emit("setEditor", {editor: v}); }, + /** + * Whether the document is fully loaded + * @property {Boolean} ready + */ + get ready(){ return ready; }, + set ready(v) { + ready = v; + emit.sticky("ready"); + }, /** * The tooltip displayed when hovering over the tab button * @property {String} tooltip @@ -487,16 +493,6 @@ define(function(require, module, exports) { * @param {Boolean} e.upload whether this is an upload (instead of a download). **/ "progress", - /** - * Fires when the initial document loaded completely - * @event once - * @param {Object} e - * @param {Number} e.loaded the number of bytes that have been downloaded/uploaded. - * @param {Number} e.total the total number of bytes for this file. - * @param {Boolean} e.complete whether the download has completed. - * @param {Boolean} e.upload whether this is an upload (instead of a download). - **/ - "complete", /** * Fires when this document is cloned * @event cone diff --git a/plugins/c9.ide.editors/tabmanager.js b/plugins/c9.ide.editors/tabmanager.js index 5f7f8b3d..dc94bb1b 100644 --- a/plugins/c9.ide.editors/tabmanager.js +++ b/plugins/c9.ide.editors/tabmanager.js @@ -1113,6 +1113,7 @@ define(function(require, module, exports) { if (!doc.meta.timestamp) doc.meta.timestamp = Date.now() - settings.timeOffset; + doc.ready = true; emit("open", { tab: tab, options: options }); callback && callback(null, tab); } @@ -1123,7 +1124,7 @@ define(function(require, module, exports) { // Hooks for plugins that want to override value and state loading var event = { - options: options, + options: options, tab: tab, loadFromDisk: loadFromDisk, setLoading: setLoading, From d40501d2f47f7f3b76884123c65fc433f1fcdced Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 16:27:02 +0000 Subject: [PATCH 06/12] Add ready variable --- plugins/c9.ide.editors/document.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index 320226f8..b8e79a20 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -26,7 +26,7 @@ define(function(require, module, exports) { var meta = {}; var hasValue = options && (typeof options.value === "string"); - var tab, lastState, title, tooltip, editor, recentValue; + var tab, lastState, title, tooltip, editor, recentValue, ready; plugin.on("newListener", function(type, listener) { if (type == "state.set" && lastState) { @@ -337,9 +337,12 @@ define(function(require, module, exports) { * @property {Boolean} ready */ get ready(){ return ready; }, - set ready(v) { - ready = v; - emit.sticky("ready"); + set ready(v) { + if (typeof ready === "undefined") { + ready = v; + emit.sticky("ready"); + } + }, /** * The tooltip displayed when hovering over the tab button From 8b2cb0ff89939031af93b18dfeaca2048456ec5a Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 19:54:08 +0000 Subject: [PATCH 07/12] Add loadtime to c9.core --- plugins/c9.core/c9.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/c9.core/c9.js b/plugins/c9.core/c9.js index 6e7f9b1c..502871af 100644 --- a/plugins/c9.core/c9.js +++ b/plugins/c9.core/c9.js @@ -37,7 +37,7 @@ define(function(require, module, exports) { if (!skipProps[prop]) plugin[prop] = options[prop]; } - var totalLoadTime; + var totalLoadTime, startLoadTime; function load() { if (loaded) return false; @@ -279,6 +279,11 @@ define(function(require, module, exports) { */ get totalLoadTime(){ return totalLoadTime; }, set totalLoadTime(v){ totalLoadTime = v; }, + /** + * + */ + get startLoadTime(){ return startLoadTime; }, + set startLoadTime(v){ startLoadTime = v; }, _events: [ /** From 79bf78086355a089fa08b9f634805f0482182f88 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 20:18:30 +0000 Subject: [PATCH 08/12] Make sure done is called async --- plugins/c9.ide.editors/tabmanager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/c9.ide.editors/tabmanager.js b/plugins/c9.ide.editors/tabmanager.js index dc94bb1b..a52b9e6c 100644 --- a/plugins/c9.ide.editors/tabmanager.js +++ b/plugins/c9.ide.editors/tabmanager.js @@ -1162,7 +1162,10 @@ define(function(require, module, exports) { }); } else { - done(null, null); + // done has to be called asynchronously + setTimeout(function() { + done(null, null); + }); } return tab; From 4ca996949ad4de4c384e7406e6460574175be2da Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 20:19:23 +0000 Subject: [PATCH 09/12] Throw if ready is set more than once --- plugins/c9.ide.editors/document.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index b8e79a20..5326b831 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -338,11 +338,9 @@ define(function(require, module, exports) { */ get ready(){ return ready; }, set ready(v) { - if (typeof ready === "undefined") { - ready = v; - emit.sticky("ready"); - } - + if (ready) throw new Error("Permission Denied"); + ready = true; + emit.sticky("ready"); }, /** * The tooltip displayed when hovering over the tab button From 0767d54dde3644817357bf4b4381d57b7a51e3e8 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 20:59:11 +0000 Subject: [PATCH 10/12] Fix tabmanager tests --- plugins/c9.ide.editors/tabmanager_test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/c9.ide.editors/tabmanager_test.js b/plugins/c9.ide.editors/tabmanager_test.js index bd7c47fa..71b72589 100644 --- a/plugins/c9.ide.editors/tabmanager_test.js +++ b/plugins/c9.ide.editors/tabmanager_test.js @@ -385,7 +385,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor1"); expect.html(tab.pane.aml.getPage("editor::texteditor").$ext).visible; @@ -410,7 +410,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor3"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -432,7 +432,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = tabs.getPanes()[1].getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor4"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -454,7 +454,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor4"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -470,13 +470,13 @@ require(["lib/architect/architect", "lib/chai/chai"], first.hsplit(); var pages = tabs.getTabs(); - expect(pages).length(6); + expect(pages).length(7); tabs.getPanes().forEach(function(pane) { pages.pop().attachTo(pane); }); expect(tabs.getPanes()).length(5); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); done(); }); @@ -494,7 +494,7 @@ require(["lib/architect/architect", "lib/chai/chai"], expect(cstate).to.deep.equal(tabs.getState()); expect(tabs.getPanes()).length(5); - expect(tabs.getTabs()).length(6); + expect(tabs.getTabs()).length(7); //Loop through state to check all the widths/heights From 363f4412d57d90d719bcd67f0f9190356bc802cf Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 21:36:17 +0000 Subject: [PATCH 11/12] Fix tabManager tests --- plugins/c9.ide.editors/tabmanager_test.js | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/c9.ide.editors/tabmanager_test.js b/plugins/c9.ide.editors/tabmanager_test.js index 71b72589..6f2c683a 100644 --- a/plugins/c9.ide.editors/tabmanager_test.js +++ b/plugins/c9.ide.editors/tabmanager_test.js @@ -247,20 +247,22 @@ require(["lib/architect/architect", "lib/chai/chai"], it('should rename a directory - change tab path', function(done) { var vpath = "/dir/stuff.json"; - tabs.openFile(vpath, function(err, tab) { - expect(tab.path).to.equal(vpath); - expect(tab.title).to.equal("stuff.json"); - - fs.rename("/dir", "/dir2", function(err) { - if (err) - throw err; - expect(tab.path).to.equal("/dir2/stuff.json"); + fs.rmdir("/dir2", { recursive: true }, function(){ + tabs.openFile(vpath, function(err, tab) { + expect(tab.path).to.equal(vpath); expect(tab.title).to.equal("stuff.json"); - tab.unload(); - fs.rename("/dir2", "/dir", function (err) { + + fs.rename("/dir", "/dir2", function(err) { if (err) throw err; - done(); + expect(tab.path).to.equal("/dir2/stuff.json"); + expect(tab.title).to.equal("stuff.json"); + tab.unload(); + fs.rename("/dir2", "/dir", function (err) { + if (err) + throw err; + done(); + }); }); }); }); From 4fcb3f2e675bcdbbdb55ca9a13a7087e6bb3bf21 Mon Sep 17 00:00:00 2001 From: Nikolai Onken Date: Wed, 11 Feb 2015 21:37:50 +0000 Subject: [PATCH 12/12] Update tab count --- plugins/c9.ide.editors/tabmanager_test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/c9.ide.editors/tabmanager_test.js b/plugins/c9.ide.editors/tabmanager_test.js index 6f2c683a..fcd05eb0 100644 --- a/plugins/c9.ide.editors/tabmanager_test.js +++ b/plugins/c9.ide.editors/tabmanager_test.js @@ -387,7 +387,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor1"); expect.html(tab.pane.aml.getPage("editor::texteditor").$ext).visible; @@ -412,7 +412,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor3"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -434,7 +434,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = tabs.getPanes()[1].getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor4"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -456,7 +456,7 @@ require(["lib/architect/architect", "lib/chai/chai"], var tab = pane.getTab(); pane.unload(); expect(tabs.getPanes()).length(1); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); tabs.focusTab(tab); expect(tab.editor.name).to.equal("texteditor4"); expect(tab.pane.aml.getPage("editor::texteditor").$ext.style.display).to.not.equal("none"); @@ -472,13 +472,13 @@ require(["lib/architect/architect", "lib/chai/chai"], first.hsplit(); var pages = tabs.getTabs(); - expect(pages).length(7); + expect(pages).length(6); tabs.getPanes().forEach(function(pane) { pages.pop().attachTo(pane); }); expect(tabs.getPanes()).length(5); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); done(); }); @@ -496,7 +496,7 @@ require(["lib/architect/architect", "lib/chai/chai"], expect(cstate).to.deep.equal(tabs.getState()); expect(tabs.getPanes()).length(5); - expect(tabs.getTabs()).length(7); + expect(tabs.getTabs()).length(6); //Loop through state to check all the widths/heights