Remote Plugins

All Cloud9 plugins are client side plugins. This means that your plugin's code is executed in the user's browser. The APIs that we provide allow you to interact with the user's workspace over the VFS (Virtual File System) protocol, providing a Filesystem , Process and Network API.

For some use cases these APIs don't suffice. For instance when you want to combine a good deal of logic with lots of file system or process interaction. Another use case is when you want to use code or binaries that are able to run in the browser. For these and other use cases Cloud9 offers an API that extends the VFS process, running in the user's workspace, with additional functionality.

Loading Remote Plugins

Your regular Cloud9 plugin is responsible for loading a remote plugin. Remote plugins can be loaded from source code in a string, or from a file that is already in the workspace. See our guide about dependency management on how to get your file(s) onto the workspace.

The following example loads a plugin from source.

var ext = imports.ext;

// Load the remote plugin
ext.loadRemotePlugin("api-name", {
    code: require("text!./server-plugin.js")
}, function(err, api) {

    // Use the remote plugin
    api.connect(5600, function(err, meta){
        var stream = meta.stream;
        stream.on("data", function(chunk){
            console.log(chunk);
        });
    });
    
});

Once we get a reference to the remote plugin, we can start using it as any other api. In this case we get a readable stream object that we use to read data from. Lets take a look at our remote plugin.

The file in our example, server-plugin.js could look like this.

module.exports = function (vfs, options, register) { 
    var Stream = require('stream');
    var net = require('net');
    var stream, server;
    
    function connect(port, callback) {
        stream = new Stream();
        stream.readable = true;
        
        server = net.createServer(function(reqStream) {
            reqStream.on("data", function(chunk) {
                stream.emit("data", chunk);
            });
        });
        server.listen(port, "localhost", function(err) {
            callback(err, !err && { stream: stream });
        });
    }
    
    register(null, {
        connect: connect
    });
};

The server plugin starts as a function that gets a reference to the vfs object as a first argument. The second argument contains the options object that was passed as an argument to loadRemotePlugin(). The last argument is the register() function that you already know from client side plugins.

The server plugin is executed in Node.js and thus all node's modules are available. In this example we use the stream and net module to open a tcp socket on localhost that allows an external client to connect to and send data to your client side plugin over a stream. The connect() function is made available for the client by setting it on the object that is passed too the register() function.

You can test if this works using telnet.

$ telnet localhost 5600
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HELLO

You should receive "HELLO" in your client plugin.

๐Ÿ‘

Always send a meta object

Remote plugins are made available on the client by forwarding methods and events. In order for VFS to know what type of data you are sending make sure to return a meta object that contains a property called stream, process, pty or api. Any other properties will be sent as copies of that data. You cannot send functions or recursive objects.

Error and Reconnect Handling

Loading a remote plugin can fail. Common cases are when there's a syntax error in your code or if the client goes offline during loading the plugin.

The vfs process is restarted when a user is disconnected for longer than a few minutes. When this happens the server no longer has your remote plugin loaded and you'll need to load it again.

This example shows how to handle errors and reload a remote plugin when needed.

var c9 = imports.c9;
var ext = imports.ext;

var remote;
function loadMyPlugin(){
    ext.loadRemotePlugin("api-name", {
        file: c9.home + "/.c9/lib/server-plugin.js"
    }, function(err, api) {
        if (err) return console.error(err);
    
        remote = api;
    });
}

loadMyPlugin();

c9.on("connect", loadMyPlugin);
c9.on("disconnect", function(){ remote = null });

In the same way as before we load the remote plugin, but this time we are doing it in a function called loadMyPlugin. We simply ignore errors and log them to the console. This is fine because we also hook the connect event of the c9 plugin. Whenever the client gets disconnected and reconnects this event is called and the remove plugin is (re)loaded again.

If somehow the plugin is still loaded we just get a reference to the same plugin. If you always want to load a new instance of your plugin set redefine: true.