Work on bonfire and make better .jshintrc files
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,3 +20,4 @@ node_modules
|
||||
*.iml
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
bower_components
|
||||
|
84
.jshintrc
84
.jshintrc
@ -1,48 +1,40 @@
|
||||
{
|
||||
/*
|
||||
* ENVIRONMENTS
|
||||
* =================
|
||||
*/
|
||||
|
||||
// Define globals exposed by Node.js.
|
||||
"node": true,
|
||||
|
||||
/*
|
||||
* ENFORCING OPTIONS
|
||||
* =================
|
||||
*/
|
||||
|
||||
// Force all variable names to use either camelCase style or UPPER_CASE
|
||||
// with underscores.
|
||||
"camelcase": true,
|
||||
|
||||
// Prohibit use of == and != in favor of === and !==.
|
||||
"eqeqeq": true,
|
||||
|
||||
// Suppress warnings about == null comparisons.
|
||||
"eqnull": true,
|
||||
|
||||
// Enforce tab width of 2 spaces.
|
||||
"indent": 2,
|
||||
|
||||
// Prohibit use of a variable before it is defined.
|
||||
"latedef": true,
|
||||
|
||||
// Require capitalized names for constructor functions.
|
||||
"newcap": true,
|
||||
|
||||
// Enforce use of single quotation marks for strings.
|
||||
"quotmark": "single",
|
||||
|
||||
// Prohibit trailing whitespace.
|
||||
"trailing": true,
|
||||
|
||||
// Prohibit use of explicitly undeclared variables.
|
||||
"undef": true,
|
||||
|
||||
// Warn when variables are defined but never used.
|
||||
"unused": true,
|
||||
|
||||
// Enforce line length to 80 characters
|
||||
"maxlen": 80
|
||||
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"browser": true, // Standard browser globals e.g. `window`, `document`.
|
||||
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
|
||||
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||
"curly": false, // Require {} for every new block or scope.
|
||||
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"latedef": true, // Prohibit variable use before definition.
|
||||
"newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef": true, // Require all non-global variables be declared before they are used.
|
||||
"unused": false, // Warn unused variables.
|
||||
"strict": false, // Require `use strict` pragma in every file.
|
||||
"trailing": true, // Prohibit trailing whitespaces.
|
||||
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||
"globals": { // Globals variables.
|
||||
"jasmine": true,
|
||||
"angular": true,
|
||||
"ApplicationConfiguration": true
|
||||
},
|
||||
"predef": [ // Extra globals.
|
||||
"define",
|
||||
"require",
|
||||
"exports",
|
||||
"module",
|
||||
"describe",
|
||||
"before",
|
||||
"beforeEach",
|
||||
"after",
|
||||
"afterEach",
|
||||
"it",
|
||||
"inject",
|
||||
"expect"
|
||||
],
|
||||
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||
"noempty": true // Prohibit use of empty blocks.
|
||||
}
|
183
app.js
183
app.js
@ -24,8 +24,8 @@ var express = require('express'),
|
||||
connectAssets = require('connect-assets'),
|
||||
|
||||
/**
|
||||
* Controllers (route handlers).
|
||||
*/
|
||||
* Controllers (route handlers).
|
||||
*/
|
||||
homeController = require('./controllers/home'),
|
||||
challengesController = require('./controllers/challenges'),
|
||||
resourcesController = require('./controllers/resources'),
|
||||
@ -34,13 +34,13 @@ var express = require('express'),
|
||||
bonfireController = require('./controllers/bonfire'),
|
||||
|
||||
/**
|
||||
* User model
|
||||
*/
|
||||
* User model
|
||||
*/
|
||||
User = require('./models/User'),
|
||||
|
||||
/**
|
||||
* API keys and Passport configuration.
|
||||
*/
|
||||
* API keys and Passport configuration.
|
||||
*/
|
||||
secrets = require('./config/secrets'),
|
||||
passportConf = require('./config/passport');
|
||||
|
||||
@ -53,10 +53,10 @@ var app = express();
|
||||
* Connect to MongoDB.
|
||||
*/
|
||||
mongoose.connect(secrets.db);
|
||||
mongoose.connection.on('error', function() {
|
||||
console.error(
|
||||
'MongoDB Connection Error. Please make sure that MongoDB is running.'
|
||||
);
|
||||
mongoose.connection.on('error', function () {
|
||||
console.error(
|
||||
'MongoDB Connection Error. Please make sure that MongoDB is running.'
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -68,20 +68,20 @@ app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'jade');
|
||||
app.use(compress());
|
||||
var oneYear = 31557600000;
|
||||
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
|
||||
app.use(express.static(__dirname + '/public', {maxAge: oneYear}));
|
||||
app.use(connectAssets({
|
||||
paths: [
|
||||
path.join(__dirname, 'public/css'),
|
||||
path.join(__dirname, 'public/js')
|
||||
path.join(__dirname, 'public/css'),
|
||||
path.join(__dirname, 'public/js')
|
||||
],
|
||||
helperContext: app.locals
|
||||
}));
|
||||
app.use(logger('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(expressValidator({
|
||||
customValidators: {
|
||||
matchRegex: function(param, regex) {
|
||||
matchRegex: function (param, regex) {
|
||||
return regex.test(param);
|
||||
}
|
||||
}
|
||||
@ -137,50 +137,50 @@ app.use(helmet.contentSecurityPolicy({
|
||||
defaultSrc: trusted,
|
||||
scriptSrc: ['*.optimizely.com', '*.aspnetcdn.com'].concat(trusted),
|
||||
'connect-src': [
|
||||
'ws://*.rafflecopter.com',
|
||||
'wss://*.rafflecopter.com',
|
||||
'https://*.rafflecopter.com',
|
||||
'ws://www.freecodecamp.com',
|
||||
'http://www.freecodecamp.com'
|
||||
'ws://*.rafflecopter.com',
|
||||
'wss://*.rafflecopter.com',
|
||||
'https://*.rafflecopter.com',
|
||||
'ws://www.freecodecamp.com',
|
||||
'http://www.freecodecamp.com'
|
||||
].concat(trusted),
|
||||
styleSrc: trusted,
|
||||
imgSrc: [
|
||||
'*.evernote.com',
|
||||
'*.amazonaws.com',
|
||||
'data:',
|
||||
'*.licdn.com',
|
||||
'*.gravatar.com',
|
||||
'*.youtube.com',
|
||||
'*.akamaihd.net',
|
||||
'graph.facebook.com',
|
||||
'*.githubusercontent.com',
|
||||
'*.googleusercontent.com',
|
||||
'*' /* allow all input since we have user submitted images for public profile*/
|
||||
'*.evernote.com',
|
||||
'*.amazonaws.com',
|
||||
'data:',
|
||||
'*.licdn.com',
|
||||
'*.gravatar.com',
|
||||
'*.youtube.com',
|
||||
'*.akamaihd.net',
|
||||
'graph.facebook.com',
|
||||
'*.githubusercontent.com',
|
||||
'*.googleusercontent.com',
|
||||
'*' /* allow all input since we have user submitted images for public profile*/
|
||||
].concat(trusted),
|
||||
fontSrc: ['*.googleapis.com'].concat(trusted),
|
||||
mediaSrc: [
|
||||
'*.amazonaws.com',
|
||||
'*.twitter.com'
|
||||
'*.amazonaws.com',
|
||||
'*.twitter.com'
|
||||
].concat(trusted),
|
||||
frameSrc: [
|
||||
'*.gitter.im',
|
||||
'*.vimeo.com',
|
||||
'*.twitter.com',
|
||||
'*.rafflecopter.com',
|
||||
'*.youtube.com'
|
||||
'*.gitter.im',
|
||||
'*.vimeo.com',
|
||||
'*.twitter.com',
|
||||
'*.rafflecopter.com',
|
||||
'*.youtube.com'
|
||||
].concat(trusted),
|
||||
reportOnly: false, // set to true if you only want to report errors
|
||||
setAllHeaders: false, // set to true if you want to set all headers
|
||||
safari5: false // set to true if you want to force buggy CSP in Safari 5
|
||||
}));
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use(function (req, res, next) {
|
||||
// Make user object available in templates.
|
||||
res.locals.user = req.user;
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use(function (req, res, next) {
|
||||
// Remember original destination before login.
|
||||
var path = req.path.split('/')[1];
|
||||
if (/auth|login|logout|signup|fonts|favicon/i.test(path)) {
|
||||
@ -191,7 +191,7 @@ app.use(function(req, res, next) {
|
||||
});
|
||||
|
||||
app.use(
|
||||
express.static(path.join(__dirname, 'public'), { maxAge: 31557600000 })
|
||||
express.static(path.join(__dirname, 'public'), {maxAge: 31557600000})
|
||||
);
|
||||
|
||||
/**
|
||||
@ -200,8 +200,8 @@ app.use(
|
||||
app.get('/', homeController.index);
|
||||
|
||||
app.get(
|
||||
'/resources/interview-questions',
|
||||
resourcesController.interviewQuestions);
|
||||
'/resources/interview-questions',
|
||||
resourcesController.interviewQuestions);
|
||||
app.get('/learn-to-code', resourcesController.learnToCode);
|
||||
app.get('/privacy', resourcesController.privacy);
|
||||
app.get('/jquery-exercises', resourcesController.jqueryExercises);
|
||||
@ -215,16 +215,16 @@ app.get('/control-shortcuts', resourcesController.deployAWebsite);
|
||||
app.get('/stats', resourcesController.stats);
|
||||
|
||||
app.get(
|
||||
'/pair-program-with-team-viewer',
|
||||
resourcesController.pairProgramWithTeamViewer
|
||||
'/pair-program-with-team-viewer',
|
||||
resourcesController.pairProgramWithTeamViewer
|
||||
);
|
||||
app.get(
|
||||
'/done-with-first-100-hours',
|
||||
resourcesController.doneWithFirst100Hours
|
||||
'/done-with-first-100-hours',
|
||||
resourcesController.doneWithFirst100Hours
|
||||
);
|
||||
app.get(
|
||||
'/programmer-interview-questions-app',
|
||||
resourcesController.programmerInterviewQuestionsApp
|
||||
'/programmer-interview-questions-app',
|
||||
resourcesController.programmerInterviewQuestionsApp
|
||||
);
|
||||
|
||||
app.get('/about', resourcesController.about);
|
||||
@ -244,9 +244,9 @@ app.post('/nonprofits', contactController.postContact);
|
||||
|
||||
// # Protected routes, user must be logged in.
|
||||
app.post(
|
||||
'/update-progress',
|
||||
passportConf.isAuthenticated,
|
||||
userController.updateProgress
|
||||
'/update-progress',
|
||||
passportConf.isAuthenticated,
|
||||
userController.updateProgress
|
||||
);
|
||||
|
||||
app.get(
|
||||
@ -274,13 +274,15 @@ app.get('/account/unlink/:provider', userController.getOauthUnlink);
|
||||
* and updates user.challengesHash & user.challengesCompleted
|
||||
*
|
||||
*/
|
||||
app.post('/completed_challenge', function(req, res) {
|
||||
app.post('/completed_challenge', function (req, res) {
|
||||
req.user.challengesHash[parseInt(req.body.challengeNumber)] =
|
||||
Math.round(+ new Date() / 1000);
|
||||
Math.round(+new Date() / 1000);
|
||||
var ch = req.user.challengesHash;
|
||||
var p = 0;
|
||||
for (var k in ch) {
|
||||
if (ch[k] > 0) { p += 1; }
|
||||
if (ch[k] > 0) {
|
||||
p += 1;
|
||||
}
|
||||
}
|
||||
req.user.points = p;
|
||||
req.user.save();
|
||||
@ -291,60 +293,60 @@ app.post('/completed_challenge', function(req, res) {
|
||||
*/
|
||||
|
||||
var passportOptions = {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
};
|
||||
|
||||
app.get('/auth/twitter', passport.authenticate('twitter'));
|
||||
app.get(
|
||||
'/auth/twitter/callback',
|
||||
passport.authenticate('twitter', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})
|
||||
'/auth/twitter/callback',
|
||||
passport.authenticate('twitter', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/login'
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/auth/linkedin',
|
||||
passport.authenticate('linkedin', {
|
||||
state: 'SOME STATE'
|
||||
})
|
||||
'/auth/linkedin',
|
||||
passport.authenticate('linkedin', {
|
||||
state: 'SOME STATE'
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/auth/linkedin/callback',
|
||||
passport.authenticate('linkedin', passportOptions)
|
||||
'/auth/linkedin/callback',
|
||||
passport.authenticate('linkedin', passportOptions)
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/auth/facebook',
|
||||
passport.authenticate('facebook', { scope: ['email', 'user_location'] })
|
||||
'/auth/facebook',
|
||||
passport.authenticate('facebook', {scope: ['email', 'user_location']})
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/auth/facebook/callback',
|
||||
passport.authenticate('facebook', passportOptions), function(req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
'/auth/facebook/callback',
|
||||
passport.authenticate('facebook', passportOptions), function (req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/auth/github', passport.authenticate('github'));
|
||||
app.get(
|
||||
'/auth/github/callback',
|
||||
passport.authenticate('github', passportOptions), function(req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
'/auth/github/callback',
|
||||
passport.authenticate('github', passportOptions), function (req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
'/auth/google',
|
||||
passport.authenticate('google', { scope: 'profile email' })
|
||||
'/auth/google',
|
||||
passport.authenticate('google', {scope: 'profile email'})
|
||||
);
|
||||
app.get(
|
||||
'/auth/google/callback',
|
||||
passport.authenticate('google', passportOptions), function(req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
'/auth/google/callback',
|
||||
passport.authenticate('google', passportOptions), function (req, res) {
|
||||
res.redirect(req.session.returnTo || '/');
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
@ -353,7 +355,6 @@ app.get(
|
||||
app.get('/bonfire', bonfireController.index);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 500 Error Handler.
|
||||
*/
|
||||
@ -362,12 +363,12 @@ app.use(errorHandler());
|
||||
/**
|
||||
* Start Express server.
|
||||
*/
|
||||
app.listen(app.get('port'), function() {
|
||||
console.log(
|
||||
'FreeCodeCamp server listening on port %d in %s mode',
|
||||
app.get('port'),
|
||||
app.get('env')
|
||||
);
|
||||
app.listen(app.get('port'), function () {
|
||||
console.log(
|
||||
'FreeCodeCamp server listening on port %d in %s mode',
|
||||
app.get('port'),
|
||||
app.get('env')
|
||||
);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
@ -17,3 +17,4 @@ exports.index = function(req, res) {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,54 +1,40 @@
|
||||
{
|
||||
/*
|
||||
* ENVIRONMENTS
|
||||
* =================
|
||||
*/
|
||||
|
||||
// Define globals exposed by modern browsers.
|
||||
"browser": true,
|
||||
|
||||
// Define globals exposed by jQuery.
|
||||
"jquery": true,
|
||||
|
||||
/*
|
||||
* ENFORCING OPTIONS
|
||||
* =================
|
||||
*/
|
||||
|
||||
// Force all variable names to use either camelCase style or UPPER_CASE
|
||||
// with underscores.
|
||||
"camelcase": true,
|
||||
|
||||
// Prohibit use of == and != in favor of === and !==.
|
||||
"eqeqeq": true,
|
||||
|
||||
// Suppress warnings about == null comparisons.
|
||||
"eqnull": true,
|
||||
|
||||
// Enforce tab width of 2 spaces.
|
||||
"indent": 2,
|
||||
|
||||
// Prohibit use of a variable before it is defined.
|
||||
"latedef": true,
|
||||
|
||||
// Require capitalized names for constructor functions.
|
||||
"newcap": true,
|
||||
|
||||
// Enforce use of single quotation marks for strings.
|
||||
"quotmark": "single",
|
||||
|
||||
// Prohibit trailing whitespace.
|
||||
"trailing": true,
|
||||
|
||||
// Prohibit use of explicitly undeclared variables.
|
||||
"undef": true,
|
||||
|
||||
// Warn when variables are defined but never used.
|
||||
"unused": true,
|
||||
|
||||
// Enforce line length to 80 characters
|
||||
"maxlen": 80,
|
||||
|
||||
// Enforce placing 'use strict' at the top function scope
|
||||
"strict": true'
|
||||
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"browser": true, // Standard browser globals e.g. `window`, `document`.
|
||||
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
|
||||
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
|
||||
"curly": false, // Require {} for every new block or scope.
|
||||
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"latedef": true, // Prohibit variable use before definition.
|
||||
"newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef": true, // Require all non-global variables be declared before they are used.
|
||||
"unused": false, // Warn unused variables.
|
||||
"strict": false, // Require `use strict` pragma in every file.
|
||||
"trailing": true, // Prohibit trailing whitespaces.
|
||||
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||
"globals": { // Globals variables.
|
||||
"jasmine": true,
|
||||
"angular": true,
|
||||
"ApplicationConfiguration": true
|
||||
},
|
||||
"predef": [ // Extra globals.
|
||||
"define",
|
||||
"require",
|
||||
"exports",
|
||||
"module",
|
||||
"describe",
|
||||
"before",
|
||||
"beforeEach",
|
||||
"after",
|
||||
"afterEach",
|
||||
"it",
|
||||
"inject",
|
||||
"expect"
|
||||
],
|
||||
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||
"noempty": true // Prohibit use of empty blocks.
|
||||
}
|
104
public/js/lib/bonfire/bonfire.js
Normal file
104
public/js/lib/bonfire/bonfire.js
Normal file
@ -0,0 +1,104 @@
|
||||
|
||||
// sends the input to the plugin for evaluation
|
||||
var submit = function(code) {
|
||||
// postpone the evaluation until the plugin is initialized
|
||||
plugin.whenConnected(
|
||||
function() {
|
||||
if (requests == 0) {
|
||||
startLoading();
|
||||
}
|
||||
|
||||
requests++;
|
||||
plugin.remote.run(code);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// prepares the string to be printed on the terminal
|
||||
var escape = function(msg) {
|
||||
return msg.
|
||||
replace(/&/g,'&').
|
||||
replace(/</g,'<').
|
||||
replace(/>/g,'>').
|
||||
replace(/\n/g, '<br/>').
|
||||
replace(/ /g, ' ');
|
||||
};
|
||||
|
||||
|
||||
// puts the message on the terminal
|
||||
var print = function(cls, msg) {
|
||||
codeOutput.setValue(escape(msg));
|
||||
};
|
||||
|
||||
|
||||
// will restart the plugin if it does not respond
|
||||
var disconnectTimeout = null;
|
||||
var startLoading = function() {
|
||||
disconnectTimeout = setTimeout(disconnect, 3000);
|
||||
};
|
||||
|
||||
var endLoading = function() {
|
||||
clearTimeout(disconnectTimeout);
|
||||
};
|
||||
|
||||
var disconnect = function() {
|
||||
plugin.disconnect();
|
||||
};
|
||||
|
||||
|
||||
// interface provided to the plugin
|
||||
var api = {
|
||||
output: function(data) {
|
||||
if (!--requests) {
|
||||
endLoading();
|
||||
}
|
||||
|
||||
print('separator');
|
||||
print('input', data.input);
|
||||
if (data.error) {
|
||||
print('message', data.error);
|
||||
} else {
|
||||
print('output', data.output);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// obtaining absolute path of this script
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var path = scripts[scripts.length-1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/')+'/';
|
||||
|
||||
|
||||
|
||||
var requests;
|
||||
|
||||
// (re)initializes the plugin
|
||||
var reset = function() {
|
||||
requests = 0;
|
||||
plugin = new jailed.Plugin(path+'plugin.js', api);
|
||||
plugin.whenDisconnected( function() {
|
||||
// give some time to handle the last responce
|
||||
setTimeout( function() {
|
||||
endLoading();
|
||||
|
||||
while (el.terminal.hasChildNodes()) {
|
||||
el.terminal.removeChild(el.terminal.childNodes[0]);
|
||||
}
|
||||
|
||||
|
||||
print('message', 'Your code took too long to execute. Check for an infinite loop or recursion.');
|
||||
|
||||
reset();
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// initialize everything
|
||||
var plugin = null;
|
||||
|
||||
reset();
|
68
public/js/lib/bonfire/plugin.js
Normal file
68
public/js/lib/bonfire/plugin.js
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
// executes the given code and handles the result
|
||||
var run = function(code) {
|
||||
var result = {
|
||||
input: code,
|
||||
output: null,
|
||||
error: null
|
||||
};
|
||||
|
||||
try {
|
||||
result.output = stringify(runHidden(code));
|
||||
} catch(e) {
|
||||
result.error = e.message;
|
||||
}
|
||||
|
||||
application.remote.output(result);
|
||||
};
|
||||
|
||||
|
||||
// protects even the worker scope from being accessed
|
||||
var runHidden = function(code) {
|
||||
var indexedDB = null;
|
||||
var location = null;
|
||||
var navigator = null;
|
||||
var onerror = null;
|
||||
var onmessage = null;
|
||||
var performance = null;
|
||||
var self = null;
|
||||
var webkitIndexedDB = null;
|
||||
var postMessage = null;
|
||||
var close = null;
|
||||
var openDatabase = null;
|
||||
var openDatabaseSync = null;
|
||||
var webkitRequestFileSystem = null;
|
||||
var webkitRequestFileSystemSync = null;
|
||||
var webkitResolveLocalFileSystemSyncURL = null;
|
||||
var webkitResolveLocalFileSystemURL = null;
|
||||
var addEventListener = null;
|
||||
var dispatchEvent = null;
|
||||
var removeEventListener = null;
|
||||
var dump = null;
|
||||
var onoffline = null;
|
||||
var ononline = null;
|
||||
var importScripts = null;
|
||||
var console = null;
|
||||
var application = null;
|
||||
|
||||
return eval(code);
|
||||
}
|
||||
|
||||
|
||||
// converts the output into a string
|
||||
var stringify = function(output) {
|
||||
var result;
|
||||
|
||||
if (typeof output == 'undefined') {
|
||||
result = 'undefined';
|
||||
} else if (output === null) {
|
||||
result = 'null';
|
||||
} else {
|
||||
result = JSON.stringify(output) || output.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
application.setInterface({run:run});
|
432
public/js/lib/jailed/_JailedSite.js
Normal file
432
public/js/lib/jailed/_JailedSite.js
Normal file
@ -0,0 +1,432 @@
|
||||
|
||||
/**
|
||||
* Contains the JailedSite object used both by the application
|
||||
* site, and by each plugin
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
/**
|
||||
* JailedSite object represents a single site in the
|
||||
* communication protocol between the application and the plugin
|
||||
*
|
||||
* @param {Object} connection a special object allowing to send
|
||||
* and receive messages from the opposite site (basically it
|
||||
* should only provide send() and onMessage() methods)
|
||||
*/
|
||||
JailedSite = function(connection) {
|
||||
this._interface = {};
|
||||
this._remote = null;
|
||||
this._remoteUpdateHandler = function(){};
|
||||
this._getInterfaceHandler = function(){};
|
||||
this._interfaceSetAsRemoteHandler = function(){};
|
||||
this._disconnectHandler = function(){};
|
||||
this._store = new ReferenceStore;
|
||||
|
||||
var me = this;
|
||||
this._connection = connection;
|
||||
this._connection.onMessage(
|
||||
function(data){ me._processMessage(data); }
|
||||
);
|
||||
|
||||
this._connection.onDisconnect(
|
||||
function(m){
|
||||
me._disconnectHandler(m);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a handler to be called when the remote site updates its
|
||||
* interface
|
||||
*
|
||||
* @param {Function} handler
|
||||
*/
|
||||
JailedSite.prototype.onRemoteUpdate = function(handler) {
|
||||
this._remoteUpdateHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a handler to be called when received a responce from the
|
||||
* remote site reporting that the previously provided interface
|
||||
* has been succesfully set as remote for that site
|
||||
*
|
||||
* @param {Function} handler
|
||||
*/
|
||||
JailedSite.prototype.onInterfaceSetAsRemote = function(handler) {
|
||||
this._interfaceSetAsRemoteHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a handler to be called when the remote site requests to
|
||||
* (re)send the interface. Used to detect an initialzation
|
||||
* completion without sending additional request, since in fact
|
||||
* 'getInterface' request is only sent by application at the last
|
||||
* step of the plugin initialization
|
||||
*
|
||||
* @param {Function} handler
|
||||
*/
|
||||
JailedSite.prototype.onGetInterface = function(handler) {
|
||||
this._getInterfaceHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @returns {Object} set of remote interface methods
|
||||
*/
|
||||
JailedSite.prototype.getRemote = function() {
|
||||
return this._remote;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the interface of this site making it available to the
|
||||
* remote site by sending a message with a set of methods names
|
||||
*
|
||||
* @param {Object} _interface to set
|
||||
*/
|
||||
JailedSite.prototype.setInterface = function(_interface) {
|
||||
this._interface = _interface;
|
||||
this._sendInterface();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the actual interface to the remote site upon it was
|
||||
* updated or by a special request of the remote site
|
||||
*/
|
||||
JailedSite.prototype._sendInterface = function() {
|
||||
var names = [];
|
||||
for (var name in this._interface) {
|
||||
if (this._interface.hasOwnProperty(name)) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
this._connection.send({type:'setInterface', api: names});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles a message from the remote site
|
||||
*/
|
||||
JailedSite.prototype._processMessage = function(data) {
|
||||
switch(data.type) {
|
||||
case 'method':
|
||||
var method = this._interface[data.name];
|
||||
var args = this._unwrap(data.args);
|
||||
method.apply(null, args);
|
||||
break;
|
||||
case 'callback':
|
||||
var method = this._store.fetch(data.id)[data.num];
|
||||
var args = this._unwrap(data.args);
|
||||
method.apply(null, args);
|
||||
break;
|
||||
case 'setInterface':
|
||||
this._setRemote(data.api);
|
||||
break;
|
||||
case 'getInterface':
|
||||
this._sendInterface();
|
||||
this._getInterfaceHandler();
|
||||
break;
|
||||
case 'interfaceSetAsRemote':
|
||||
this._interfaceSetAsRemoteHandler();
|
||||
break;
|
||||
case 'disconnect':
|
||||
this._disconnectHandler();
|
||||
this._connection.disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a requests to the remote site asking it to provide its
|
||||
* current interface
|
||||
*/
|
||||
JailedSite.prototype.requestRemote = function() {
|
||||
this._connection.send({type:'getInterface'});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the new remote interface provided by the other site
|
||||
*
|
||||
* @param {Array} names list of function names
|
||||
*/
|
||||
JailedSite.prototype._setRemote = function(names) {
|
||||
this._remote = {};
|
||||
var i, name;
|
||||
for (i = 0; i < names.length; i++) {
|
||||
name = names[i];
|
||||
this._remote[name] = this._genRemoteMethod(name);
|
||||
}
|
||||
|
||||
this._remoteUpdateHandler();
|
||||
this._reportRemoteSet();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the wrapped function corresponding to a single remote
|
||||
* method. When the generated function is called, it will send the
|
||||
* corresponding message to the remote site asking it to execute
|
||||
* the particular method of its interface
|
||||
*
|
||||
* @param {String} name of the remote method
|
||||
*
|
||||
* @returns {Function} wrapped remote method
|
||||
*/
|
||||
JailedSite.prototype._genRemoteMethod = function(name) {
|
||||
var me = this;
|
||||
var remoteMethod = function() {
|
||||
me._connection.send({
|
||||
type: 'method',
|
||||
name: name,
|
||||
args: me._wrap(arguments)
|
||||
});
|
||||
};
|
||||
|
||||
return remoteMethod;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a responce reporting that interface just provided by the
|
||||
* remote site was sucessfully set by this site as remote
|
||||
*/
|
||||
JailedSite.prototype._reportRemoteSet = function() {
|
||||
this._connection.send({type:'interfaceSetAsRemote'});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepares the provided set of remote method arguments for
|
||||
* sending to the remote site, replaces all the callbacks with
|
||||
* identifiers
|
||||
*
|
||||
* @param {Array} args to wrap
|
||||
*
|
||||
* @returns {Array} wrapped arguments
|
||||
*/
|
||||
JailedSite.prototype._wrap = function(args) {
|
||||
var wrapped = [];
|
||||
var callbacks = {};
|
||||
var callbacksPresent = false;
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (typeof args[i] == 'function') {
|
||||
callbacks[i] = args[i];
|
||||
wrapped[i] = {type: 'callback', num : i};
|
||||
callbacksPresent = true;
|
||||
} else {
|
||||
wrapped[i] = {type: 'argument', value : args[i]};
|
||||
}
|
||||
}
|
||||
|
||||
var result = {args: wrapped};
|
||||
|
||||
if (callbacksPresent) {
|
||||
result.callbackId = this._store.put(callbacks);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps the set of arguments delivered from the remote site,
|
||||
* replaces all callback identifiers with a function which will
|
||||
* initiate sending that callback identifier back to other site
|
||||
*
|
||||
* @param {Object} args to unwrap
|
||||
*
|
||||
* @returns {Array} unwrapped args
|
||||
*/
|
||||
JailedSite.prototype._unwrap = function(args) {
|
||||
var called = false;
|
||||
|
||||
// wraps each callback so that the only one could be called
|
||||
var once = function(cb) {
|
||||
return function() {
|
||||
if (!called) {
|
||||
called = true;
|
||||
cb.apply(this, arguments);
|
||||
} else {
|
||||
var msg =
|
||||
'A callback from this set has already been executed';
|
||||
throw new Error(msg);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var result = [];
|
||||
var i, arg, cb, me = this;
|
||||
for (i = 0; i < args.args.length; i++) {
|
||||
arg = args.args[i];
|
||||
if (arg.type == 'argument') {
|
||||
result.push(arg.value);
|
||||
} else {
|
||||
cb = once(
|
||||
this._genRemoteCallback(args.callbackId, i)
|
||||
);
|
||||
result.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the wrapped function corresponding to a single remote
|
||||
* callback. When the generated function is called, it will send
|
||||
* the corresponding message to the remote site asking it to
|
||||
* execute the particular callback previously saved during a call
|
||||
* by the remote site a method from the interface of this site
|
||||
*
|
||||
* @param {Number} id of the remote callback to execute
|
||||
* @param {Number} argNum argument index of the callback
|
||||
*
|
||||
* @returns {Function} wrapped remote callback
|
||||
*/
|
||||
JailedSite.prototype._genRemoteCallback = function(id, argNum) {
|
||||
var me = this;
|
||||
var remoteCallback = function() {
|
||||
me._connection.send({
|
||||
type : 'callback',
|
||||
id : id,
|
||||
num : argNum,
|
||||
args : me._wrap(arguments)
|
||||
});
|
||||
};
|
||||
|
||||
return remoteCallback;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the notification message and breaks the connection
|
||||
*/
|
||||
JailedSite.prototype.disconnect = function() {
|
||||
this._connection.send({type: 'disconnect'});
|
||||
this._connection.disconnect();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a handler to be called when received a disconnect message
|
||||
* from the remote site
|
||||
*
|
||||
* @param {Function} handler
|
||||
*/
|
||||
JailedSite.prototype.onDisconnect = function(handler) {
|
||||
this._disconnectHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ReferenceStore is a special object which stores other objects
|
||||
* and provides the references (number) instead. This reference
|
||||
* may then be sent over a json-based communication channel (IPC
|
||||
* to another Node.js process or a message to the Worker). Other
|
||||
* site may then provide the reference in the responce message
|
||||
* implying the given object should be activated.
|
||||
*
|
||||
* Primary usage for the ReferenceStore is a storage for the
|
||||
* callbacks, which therefore makes it possible to initiate a
|
||||
* callback execution by the opposite site (which normally cannot
|
||||
* directly execute functions over the communication channel).
|
||||
*
|
||||
* Each stored object can only be fetched once and is not
|
||||
* available for the second time. Each stored object must be
|
||||
* fetched, since otherwise it will remain stored forever and
|
||||
* consume memory.
|
||||
*
|
||||
* Stored object indeces are simply the numbers, which are however
|
||||
* released along with the objects, and are later reused again (in
|
||||
* order to postpone the overflow, which should not likely happen,
|
||||
* but anyway).
|
||||
*/
|
||||
var ReferenceStore = function() {
|
||||
this._store = {}; // stored object
|
||||
this._indices = [0]; // smallest available indices
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @function _genId() generates the new reference id
|
||||
*
|
||||
* @returns {Number} smallest available id and reserves it
|
||||
*/
|
||||
ReferenceStore.prototype._genId = function() {
|
||||
var id;
|
||||
if (this._indices.length == 1) {
|
||||
id = this._indices[0]++;
|
||||
} else {
|
||||
id = this._indices.shift();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Releases the given reference id so that it will be available by
|
||||
* another object stored
|
||||
*
|
||||
* @param {Number} id to release
|
||||
*/
|
||||
ReferenceStore.prototype._releaseId = function(id) {
|
||||
for (var i = 0; i < this._indices.length; i++) {
|
||||
if (id < this._indices[i]) {
|
||||
this._indices.splice(i, 0, id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// cleaning-up the sequence tail
|
||||
for (i = this._indices.length-1; i >= 0; i--) {
|
||||
if (this._indices[i]-1 == this._indices[i-1]) {
|
||||
this._indices.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores the given object and returns the refernce id instead
|
||||
*
|
||||
* @param {Object} obj to store
|
||||
*
|
||||
* @returns {Number} reference id of the stored object
|
||||
*/
|
||||
ReferenceStore.prototype.put = function(obj) {
|
||||
var id = this._genId();
|
||||
this._store[id] = obj;
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves previously stored object and releases its reference
|
||||
*
|
||||
* @param {Number} id of an object to retrieve
|
||||
*/
|
||||
ReferenceStore.prototype.fetch = function(id) {
|
||||
var obj = this._store[id];
|
||||
this._store[id] = null;
|
||||
delete this._store[id];
|
||||
this._releaseId(id);
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
})();
|
||||
|
1
public/js/lib/jailed/_frame.html
Normal file
1
public/js/lib/jailed/_frame.html
Normal file
@ -0,0 +1 @@
|
||||
<script src="_frame.js"></script>
|
49
public/js/lib/jailed/_frame.js
Normal file
49
public/js/lib/jailed/_frame.js
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
/**
|
||||
* Contains the code executed in the sandboxed frame under web-browser
|
||||
*
|
||||
* Creates a Web-Worker inside the frame, sets up the communication
|
||||
* between the worker and the parent window
|
||||
*/
|
||||
|
||||
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var __jailed__path__ = scripts[scripts.length-1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/')+'/';
|
||||
|
||||
// creating worker as a blob enables import of local files
|
||||
var blobCode = [
|
||||
' self.addEventListener("message", function(m){ ',
|
||||
' if (m.data.type == "initImport") { ',
|
||||
' importScripts(m.data.url); ',
|
||||
' self.postMessage({type: "initialized"}); ',
|
||||
' } ',
|
||||
' }); '
|
||||
].join('\n');
|
||||
|
||||
var blobUrl = window.URL.createObjectURL(
|
||||
new Blob([blobCode])
|
||||
);
|
||||
|
||||
|
||||
var worker = new Worker(blobUrl);
|
||||
|
||||
// telling worker to load _pluginWeb.js (see blob code above)
|
||||
worker.postMessage({
|
||||
type: 'initImport',
|
||||
url: __jailed__path__ + '_pluginWeb.js'
|
||||
});
|
||||
|
||||
|
||||
// forwarding messages between the worker and parent window
|
||||
worker.addEventListener('message', function(m) {
|
||||
parent.postMessage(m.data, '*');
|
||||
});
|
||||
|
||||
window.addEventListener('message', function(m) {
|
||||
worker.postMessage(m.data);
|
||||
});
|
||||
|
95
public/js/lib/jailed/_pluginCore.js
Normal file
95
public/js/lib/jailed/_pluginCore.js
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
/**
|
||||
* Core plugin script loaded into the plugin process/thread.
|
||||
*
|
||||
* Initializes the plugin-site API global methods.
|
||||
*/
|
||||
|
||||
(function(){
|
||||
|
||||
// localize
|
||||
var site = new JailedSite(connection);
|
||||
delete JailedSite;
|
||||
delete connection;
|
||||
|
||||
site.onGetInterface(function(){
|
||||
launchConnected();
|
||||
});
|
||||
|
||||
site.onRemoteUpdate(function(){
|
||||
application.remote = site.getRemote();
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Simplified clone of Whenable instance (the object can not be
|
||||
* placed into a shared script, because the main library needs it
|
||||
* before the additional scripts may load)
|
||||
*/
|
||||
var connected = false;
|
||||
var connectedHandlers = [];
|
||||
|
||||
var launchConnected = function() {
|
||||
if (!connected) {
|
||||
connected = true;
|
||||
|
||||
var handler;
|
||||
while(handler = connectedHandlers.pop()) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var checkHandler = function(handler){
|
||||
var type = typeof handler;
|
||||
if (type != 'function') {
|
||||
var msg =
|
||||
'A function may only be subsribed to the event, '
|
||||
+ type
|
||||
+ ' was provided instead'
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a function executed after the connection to the
|
||||
* application is estaplished, and the initial interface-exchange
|
||||
* messaging is completed
|
||||
*
|
||||
* @param {Function} handler to be called upon initialization
|
||||
*/
|
||||
application.whenConnected = function(handler) {
|
||||
handler = checkHandler(handler);
|
||||
if (connected) {
|
||||
handler();
|
||||
} else {
|
||||
connectedHandlers.push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the plugin interface available to the application
|
||||
*
|
||||
* @param {Object} _interface to set
|
||||
*/
|
||||
application.setInterface = function(_interface) {
|
||||
site.setInterface(_interface);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the plugin from the application (sending
|
||||
* notification message) and destroys itself
|
||||
*/
|
||||
application.disconnect = function(_interface) {
|
||||
site.disconnect();
|
||||
}
|
||||
|
||||
})();
|
||||
|
267
public/js/lib/jailed/_pluginNode.js
Normal file
267
public/js/lib/jailed/_pluginNode.js
Normal file
@ -0,0 +1,267 @@
|
||||
|
||||
/**
|
||||
* Contains the routines loaded by the plugin process under Node.js
|
||||
*
|
||||
* Initializes the Node.js environment version of the
|
||||
* platform-dependent connection object for the plugin site
|
||||
*/
|
||||
|
||||
application = {};
|
||||
connection = {};
|
||||
|
||||
|
||||
/**
|
||||
* Prints error message and its stack
|
||||
*
|
||||
* @param {Object} msg stack provided by error.stack or a message
|
||||
*/
|
||||
var printError = function(msg) {
|
||||
console.error();
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Event lisener for the plugin message
|
||||
*/
|
||||
process.on('message', function(m) {
|
||||
switch(m.type){
|
||||
case 'import':
|
||||
importScript(m.url);
|
||||
break;
|
||||
case 'importJailed':
|
||||
importScriptJailed(m.url);
|
||||
break;
|
||||
case 'execute':
|
||||
execute(m.code);
|
||||
break;
|
||||
case 'message':
|
||||
// unhandled exception would break the IPC channel
|
||||
try {
|
||||
conn._messageHandler(m.data);
|
||||
} catch(e) {
|
||||
printError(e.stack);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the given path is remote
|
||||
*
|
||||
* @param {String} path to check
|
||||
* @returns {Boolean} true if path is remote
|
||||
*/
|
||||
var isRemote = function(path) {
|
||||
return (path.substr(0,7).toLowerCase() == 'http://' ||
|
||||
path.substr(0,8).toLowerCase() == 'https://');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads and executes the JavaScript file with the given url
|
||||
*
|
||||
* @param {String} url of the script to load
|
||||
*/
|
||||
var importScript = function(url) {
|
||||
var sCb = function() {
|
||||
process.send({type: 'importSuccess', url: url});
|
||||
}
|
||||
|
||||
var fCb = function() {
|
||||
process.send({type: 'importFailure', url: url});
|
||||
}
|
||||
|
||||
var run = function(code) {
|
||||
executeNormal(code, url, sCb, fCb);
|
||||
}
|
||||
|
||||
if (isRemote(url)) {
|
||||
loadRemote(url, run, fCb);
|
||||
} else {
|
||||
try {
|
||||
run(loadLocal(url));
|
||||
} catch(e) {
|
||||
printError(e.stack);
|
||||
fCb();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads and executes the JavaScript file with the given url in a
|
||||
* jailed environment
|
||||
*
|
||||
* @param {String} url of the script to load
|
||||
*/
|
||||
var importScriptJailed = function(url) {
|
||||
var sCb = function() {
|
||||
process.send({type: 'importSuccess', url: url});
|
||||
}
|
||||
|
||||
var fCb = function() {
|
||||
process.send({type: 'importFailure', url: url});
|
||||
}
|
||||
|
||||
var run = function(code) {
|
||||
executeJailed(code, url, sCb, fCb);
|
||||
}
|
||||
|
||||
if (isRemote(url)) {
|
||||
loadRemote(url, run, fCb);
|
||||
} else {
|
||||
try {
|
||||
run(loadLocal(url));
|
||||
} catch (e) {
|
||||
printError(e.stack);
|
||||
fCb();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given code in the jailed environment, sends the
|
||||
* corresponding message to the application site when succeeded/failed
|
||||
*
|
||||
* @param {String} code to execute
|
||||
*/
|
||||
var execute = function(code) {
|
||||
var sCb = function() {
|
||||
process.send({type: 'executeSuccess'});
|
||||
}
|
||||
|
||||
var fCb = function() {
|
||||
process.send({type: 'executeFailure'});
|
||||
}
|
||||
|
||||
executeJailed(code, 'DYNAMIC PLUGIN', sCb, fCb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given code in the current environment / scope, runs
|
||||
* the corresponding callback when done
|
||||
*
|
||||
* @param {String} code to execute
|
||||
* @param {String} url of the script (for displaying the stack)
|
||||
* @param {Function} sCb
|
||||
* @param {Function} fCb
|
||||
*/
|
||||
var executeNormal = function(code, url, sCb, fCb) {
|
||||
var err = null;
|
||||
try {
|
||||
require('vm').runInThisContext(code, url);
|
||||
sCb();
|
||||
} catch (e) {
|
||||
printError(e.stack);
|
||||
fCb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given code in a jailed environment, runs the
|
||||
* corresponding callback when done
|
||||
*
|
||||
* @param {String} code to execute
|
||||
* @param {String} url of the script (for displaying the stack)
|
||||
* @param {Function} sCb
|
||||
* @param {Function} fCb
|
||||
*/
|
||||
var executeJailed = function(code, url, sCb, fCb) {
|
||||
var vm = require('vm');
|
||||
var sandbox = {};
|
||||
var expose = [
|
||||
'application',
|
||||
'setTimeout',
|
||||
'setInterval',
|
||||
'clearTimeout',
|
||||
'clearInterval'
|
||||
];
|
||||
|
||||
for (var i = 0; i < expose.length; i++) {
|
||||
sandbox[expose[i]] = global[expose[i]];
|
||||
}
|
||||
|
||||
code = '"use strict";\n'+code;
|
||||
try {
|
||||
vm.runInNewContext(code, vm.createContext(sandbox), url);
|
||||
sCb();
|
||||
} catch (e) {
|
||||
printError(e.stack);
|
||||
fCb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads local file and
|
||||
*
|
||||
* @param {String} path of the file to read
|
||||
*
|
||||
* @returns {String} file contents
|
||||
*/
|
||||
var loadLocal = function(path) {
|
||||
return require("fs").readFileSync(path).toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Downloads the script by remote url and provides its content as a
|
||||
* string to the callback
|
||||
*
|
||||
* @param {String} url of the remote module to load
|
||||
* @param {Function} sCb success callback
|
||||
* @param {Function} fCb failure callback
|
||||
*/
|
||||
var loadRemote = function(url, sCb, fCb) {
|
||||
var receive = function(res) {
|
||||
if (res.statusCode != 200) {
|
||||
var msg = 'Failed to load ' + url + '\n' +
|
||||
'HTTP responce status code: ' + res.statusCode;
|
||||
printError(msg);
|
||||
fCb();
|
||||
} else {
|
||||
var content = '';
|
||||
res.on('end', function(){ sCb(content); });
|
||||
res.on(
|
||||
'readable',
|
||||
function() {
|
||||
var chunk = res.read();
|
||||
content += chunk.toString();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
require('http').get(url, receive).on('error', fCb);
|
||||
} catch (e) {
|
||||
printError(e.stack);
|
||||
fCb();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connection object provided to the SandboxedSite constructor, plugin
|
||||
* site implementation for the Node.js environment
|
||||
*/
|
||||
var conn = {
|
||||
disconnect: function(){ process.exit(); },
|
||||
send: function(data) {
|
||||
process.send({type: 'message', data: data});
|
||||
},
|
||||
onMessage: function(h){ conn._messageHandler = h; },
|
||||
_messageHandler: function(){},
|
||||
onDisconnect: function() {}
|
||||
};
|
||||
|
||||
connection = conn;
|
||||
|
96
public/js/lib/jailed/_pluginWeb.js
Normal file
96
public/js/lib/jailed/_pluginWeb.js
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
/**
|
||||
* Contains the routines loaded by the plugin Worker under web-browser.
|
||||
*
|
||||
* Initializes the web environment version of the platform-dependent
|
||||
* connection object for the plugin site
|
||||
*/
|
||||
|
||||
self.application = {};
|
||||
self.connection = {};
|
||||
|
||||
|
||||
(function(){
|
||||
|
||||
/**
|
||||
* Event lisener for the plugin message
|
||||
*/
|
||||
self.addEventListener('message', function(e){
|
||||
var m = e.data.data;
|
||||
switch (m.type) {
|
||||
case 'import':
|
||||
case 'importJailed': // already jailed in the Worker
|
||||
importScript(m.url);
|
||||
break;
|
||||
case 'execute':
|
||||
execute(m.code);
|
||||
break;
|
||||
case 'message':
|
||||
conn._messageHandler(m.data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Loads and executes the JavaScript file with the given url
|
||||
*
|
||||
* @param {String} url to load
|
||||
*/
|
||||
var importScript = function(url) {
|
||||
var error = null;
|
||||
try {
|
||||
importScripts(url);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
self.postMessage({type: 'importFailure', url: url});
|
||||
throw error;
|
||||
} else {
|
||||
self.postMessage({type: 'importSuccess', url: url});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Executes the given code in a jailed environment. For web
|
||||
* implementation, we're already jailed in the worker, so simply
|
||||
* eval()
|
||||
*
|
||||
* @param {String} code code to execute
|
||||
*/
|
||||
var execute = function(code) {
|
||||
try {
|
||||
eval(code);
|
||||
} catch (e) {
|
||||
self.postMessage({type: 'executeFailure'});
|
||||
throw e;
|
||||
}
|
||||
|
||||
self.postMessage({type: 'executeSuccess'});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connection object provided to the JailedSite constructor,
|
||||
* plugin site implementation for the web-based environment.
|
||||
* Global will be then cleared to prevent exposure into the
|
||||
* Worker, so we put this local connection object into a closure
|
||||
*/
|
||||
var conn = {
|
||||
disconnect: function(){ self.close(); },
|
||||
send: function(data) {
|
||||
self.postMessage({type: 'message', data: data});
|
||||
},
|
||||
onMessage: function(h){ conn._messageHandler = h; },
|
||||
_messageHandler: function(){},
|
||||
onDisconnect: function() {}
|
||||
};
|
||||
|
||||
connection = conn;
|
||||
|
||||
})();
|
||||
|
780
public/js/lib/jailed/jailed.js
Normal file
780
public/js/lib/jailed/jailed.js
Normal file
@ -0,0 +1,780 @@
|
||||
/**
|
||||
* @fileoverview Jailed - safe yet flexible sandbox
|
||||
* @version 0.2.0
|
||||
*
|
||||
* @license MIT, see http://github.com/asvd/jailed
|
||||
* Copyright (c) 2014 asvd <heliosframework@gmail.com>
|
||||
*
|
||||
* Main library script, the only one to be loaded by a developer into
|
||||
* the application. Other scrips shipped along will be loaded by the
|
||||
* library either here (application site), or into the plugin site
|
||||
* (Worker/child process):
|
||||
*
|
||||
* _JailedSite.js loaded into both applicaiton and plugin sites
|
||||
* _frame.html sandboxed frame (web)
|
||||
* _frame.js sandboxed frame code (web)
|
||||
* _pluginWeb.js platform-dependent plugin routines (web)
|
||||
* _pluginNode.js platform-dependent plugin routines (Node.js)
|
||||
* _pluginCore.js common plugin site protocol implementation
|
||||
*/
|
||||
|
||||
|
||||
var __jailed__path__;
|
||||
if (typeof window == 'undefined') {
|
||||
// Node.js
|
||||
__jailed__path__ = __dirname + '/';
|
||||
} else {
|
||||
// web
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
__jailed__path__ = scripts[scripts.length-1].src
|
||||
.split('?')[0]
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/')+'/';
|
||||
}
|
||||
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['exports'], factory);
|
||||
} else if (typeof exports !== 'undefined') {
|
||||
factory(exports);
|
||||
} else {
|
||||
factory((root.jailed = {}));
|
||||
}
|
||||
}(this, function (exports) {
|
||||
var isNode = typeof window == 'undefined';
|
||||
|
||||
|
||||
/**
|
||||
* A special kind of event:
|
||||
* - which can only be emitted once;
|
||||
* - executes a set of subscribed handlers upon emission;
|
||||
* - if a handler is subscribed after the event was emitted, it
|
||||
* will be invoked immideately.
|
||||
*
|
||||
* Used for the events which only happen once (or do not happen at
|
||||
* all) during a single plugin lifecycle - connect, disconnect and
|
||||
* connection failure
|
||||
*/
|
||||
var Whenable = function() {
|
||||
this._emitted = false;
|
||||
this._handlers = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emits the Whenable event, calls all the handlers already
|
||||
* subscribed, switches the object to the 'emitted' state (when
|
||||
* all future subscibed listeners will be immideately issued
|
||||
* instead of being stored)
|
||||
*/
|
||||
Whenable.prototype.emit = function(){
|
||||
if (!this._emitted) {
|
||||
this._emitted = true;
|
||||
|
||||
var handler;
|
||||
while(handler = this._handlers.pop()) {
|
||||
setTimeout(handler,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the provided function as a handler for the Whenable
|
||||
* event. This handler will then be called upon the event emission
|
||||
* (if it has not been emitted yet), or will be scheduled for
|
||||
* immediate issue (if the event has already been emmitted before)
|
||||
*
|
||||
* @param {Function} handler to subscribe for the event
|
||||
*/
|
||||
Whenable.prototype.whenEmitted = function(handler){
|
||||
handler = this._checkHandler(handler);
|
||||
if (this._emitted) {
|
||||
setTimeout(handler, 0);
|
||||
} else {
|
||||
this._handlers.push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the provided object is suitable for being subscribed
|
||||
* to the event (= is a function), throws an exception if not
|
||||
*
|
||||
* @param {Object} obj to check for being subscribable
|
||||
*
|
||||
* @throws {Exception} if object is not suitable for subscription
|
||||
*
|
||||
* @returns {Object} the provided object if yes
|
||||
*/
|
||||
Whenable.prototype._checkHandler = function(handler){
|
||||
var type = typeof handler;
|
||||
if (type != 'function') {
|
||||
var msg =
|
||||
'A function may only be subsribed to the event, '
|
||||
+ type
|
||||
+ ' was provided instead'
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the library site for Node.js environment (loads
|
||||
* _JailedSite.js)
|
||||
*/
|
||||
var initNode = function() {
|
||||
require('./_JailedSite.js');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the library site for web environment (loads
|
||||
* _JailedSite.js)
|
||||
*/
|
||||
var platformInit;
|
||||
var initWeb = function() {
|
||||
// loads additional script to the application environment
|
||||
var load = function(path, cb) {
|
||||
var script = document.createElement('script');
|
||||
script.src = path;
|
||||
|
||||
var clear = function() {
|
||||
script.onload = null;
|
||||
script.onerror = null;
|
||||
script.onreadystatechange = null;
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
|
||||
var success = function() {
|
||||
clear();
|
||||
cb();
|
||||
}
|
||||
|
||||
script.onerror = clear;
|
||||
script.onload = success;
|
||||
script.onreadystatechange = function() {
|
||||
var state = script.readyState;
|
||||
if (state==='loaded' || state==='complete') {
|
||||
success();
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
platformInit = new Whenable;
|
||||
var origOnload = window.onload || function(){};
|
||||
|
||||
window.onload = function(){
|
||||
origOnload();
|
||||
load(
|
||||
__jailed__path__+'_JailedSite.js',
|
||||
function(){ platformInit.emit(); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var BasicConnection;
|
||||
|
||||
/**
|
||||
* Creates the platform-dependent BasicConnection object in the
|
||||
* Node.js environment
|
||||
*/
|
||||
var basicConnectionNode = function() {
|
||||
var childProcess = require('child_process');
|
||||
|
||||
/**
|
||||
* Platform-dependent implementation of the BasicConnection
|
||||
* object, initializes the plugin site and provides the basic
|
||||
* messaging-based connection with it
|
||||
*
|
||||
* For Node.js the plugin is created as a forked process
|
||||
*/
|
||||
BasicConnection = function() {
|
||||
this._disconnected = false;
|
||||
this._messageHandler = function(){};
|
||||
this._disconnectHandler = function(){};
|
||||
this._process = childProcess.fork(
|
||||
__jailed__path__+'_pluginNode.js'
|
||||
);
|
||||
|
||||
var me = this;
|
||||
this._process.on('message', function(m){
|
||||
me._messageHandler(m);
|
||||
});
|
||||
|
||||
this._process.on('exit', function(m){
|
||||
me._disconnected = true;
|
||||
me._disconnectHandler(m);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets-up the handler to be called upon the BasicConnection
|
||||
* initialization is completed.
|
||||
*
|
||||
* For Node.js the connection is fully initialized within the
|
||||
* constructor, so simply calls the provided handler.
|
||||
*
|
||||
* @param {Function} handler to be called upon connection init
|
||||
*/
|
||||
BasicConnection.prototype.whenInit = function(handler) {
|
||||
handler();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to the plugin site
|
||||
*
|
||||
* @param {Object} data to send
|
||||
*/
|
||||
BasicConnection.prototype.send = function(data) {
|
||||
if (!this._disconnected) {
|
||||
this._process.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for a message received from the plugin site
|
||||
*
|
||||
* @param {Function} handler to call upon a message
|
||||
*/
|
||||
BasicConnection.prototype.onMessage = function(handler) {
|
||||
this._messageHandler = function(data) {
|
||||
// broken stack would break the IPC in Node.js
|
||||
try {
|
||||
handler(data);
|
||||
} catch (e) {
|
||||
console.error();
|
||||
console.error(e.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for the event of plugin disconnection
|
||||
* (= plugin process exit)
|
||||
*
|
||||
* @param {Function} handler to call upon a disconnect
|
||||
*/
|
||||
BasicConnection.prototype.onDisconnect = function(handler) {
|
||||
this._disconnectHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the plugin (= kills the forked process)
|
||||
*/
|
||||
BasicConnection.prototype.disconnect = function() {
|
||||
this._process.kill('SIGKILL');
|
||||
this._disconnected = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the platform-dependent BasicConnection object in the
|
||||
* web-browser environment
|
||||
*/
|
||||
var basicConnectionWeb = function() {
|
||||
var perm = ['allow-scripts'];
|
||||
|
||||
if (__jailed__path__.substr(0,7).toLowerCase() == 'file://') {
|
||||
// local instance requires extra permission
|
||||
perm.push('allow-same-origin');
|
||||
}
|
||||
|
||||
// frame element to be cloned
|
||||
var sample = document.createElement('iframe');
|
||||
sample.src = __jailed__path__ + '_frame.html';
|
||||
sample.sandbox = perm.join(' ');
|
||||
sample.style.display = 'none';
|
||||
|
||||
|
||||
/**
|
||||
* Platform-dependent implementation of the BasicConnection
|
||||
* object, initializes the plugin site and provides the basic
|
||||
* messaging-based connection with it
|
||||
*
|
||||
* For the web-browser environment, the plugin is created as a
|
||||
* Worker in a sandbaxed frame
|
||||
*/
|
||||
BasicConnection = function() {
|
||||
this._init = new Whenable;
|
||||
this._disconnected = false;
|
||||
|
||||
var me = this;
|
||||
platformInit.whenEmitted(function() {
|
||||
if (!me._disconnected) {
|
||||
me._frame = sample.cloneNode(false);
|
||||
document.body.appendChild(me._frame);
|
||||
|
||||
window.addEventListener('message', function (e) {
|
||||
if (e.origin === "null" &&
|
||||
e.source === me._frame.contentWindow) {
|
||||
if (e.data.type == 'initialized') {
|
||||
me._init.emit();
|
||||
} else {
|
||||
me._messageHandler(e.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets-up the handler to be called upon the BasicConnection
|
||||
* initialization is completed.
|
||||
*
|
||||
* For the web-browser environment, the handler is issued when
|
||||
* the plugin worker successfully imported and executed the
|
||||
* _pluginWeb.js, and replied to the application site with the
|
||||
* initImprotSuccess message.
|
||||
*
|
||||
* @param {Function} handler to be called upon connection init
|
||||
*/
|
||||
BasicConnection.prototype.whenInit = function(handler) {
|
||||
this._init.whenEmitted(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to the plugin site
|
||||
*
|
||||
* @param {Object} data to send
|
||||
*/
|
||||
BasicConnection.prototype.send = function(data) {
|
||||
this._frame.contentWindow.postMessage(
|
||||
{type: 'message', data: data}, '*'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for a message received from the plugin site
|
||||
*
|
||||
* @param {Function} handler to call upon a message
|
||||
*/
|
||||
BasicConnection.prototype.onMessage = function(handler) {
|
||||
this._messageHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for the event of plugin disconnection
|
||||
* (not used in case of Worker)
|
||||
*
|
||||
* @param {Function} handler to call upon a disconnect
|
||||
*/
|
||||
BasicConnection.prototype.onDisconnect = function(){};
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the plugin (= kills the frame)
|
||||
*/
|
||||
BasicConnection.prototype.disconnect = function() {
|
||||
if (!this._disconnected) {
|
||||
this._disconnected = true;
|
||||
if (typeof this._frame != 'undefined') {
|
||||
this._frame.parentNode.removeChild(this._frame);
|
||||
} // otherwise farme is not yet created
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (isNode) {
|
||||
initNode();
|
||||
basicConnectionNode();
|
||||
} else {
|
||||
initWeb();
|
||||
basicConnectionWeb();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Application-site Connection object constructon, reuses the
|
||||
* platform-dependent BasicConnection declared above in order to
|
||||
* communicate with the plugin environment, implements the
|
||||
* application-site protocol of the interraction: provides some
|
||||
* methods for loading scripts and executing the given code in the
|
||||
* plugin
|
||||
*/
|
||||
var Connection = function(){
|
||||
this._platformConnection = new BasicConnection;
|
||||
|
||||
this._importCallbacks = {};
|
||||
this._executeSCb = function(){};
|
||||
this._executeFCb = function(){};
|
||||
this._messageHandler = function(){};
|
||||
|
||||
var me = this;
|
||||
this.whenInit = function(cb){
|
||||
me._platformConnection.whenInit(cb);
|
||||
};
|
||||
|
||||
this._platformConnection.onMessage(function(m) {
|
||||
switch(m.type) {
|
||||
case 'message':
|
||||
me._messageHandler(m.data);
|
||||
break;
|
||||
case 'importSuccess':
|
||||
me._handleImportSuccess(m.url);
|
||||
break;
|
||||
case 'importFailure':
|
||||
me._handleImportFailure(m.url);
|
||||
break;
|
||||
case 'executeSuccess':
|
||||
me._executeSCb();
|
||||
break;
|
||||
case 'executeFailure':
|
||||
me._executeFCb();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells the plugin to load a script with the given path, and to
|
||||
* execute it. Callbacks executed upon the corresponding responce
|
||||
* message from the plugin site
|
||||
*
|
||||
* @param {String} path of a script to load
|
||||
* @param {Function} sCb to call upon success
|
||||
* @param {Function} fCb to call upon failure
|
||||
*/
|
||||
Connection.prototype.importScript = function(path, sCb, fCb) {
|
||||
var f = function(){};
|
||||
this._importCallbacks[path] = {sCb: sCb||f, fCb: fCb||f};
|
||||
this._platformConnection.send({type: 'import', url: path});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells the plugin to load a script with the given path, and to
|
||||
* execute it in the JAILED environment. Callbacks executed upon
|
||||
* the corresponding responce message from the plugin site
|
||||
*
|
||||
* @param {String} path of a script to load
|
||||
* @param {Function} sCb to call upon success
|
||||
* @param {Function} fCb to call upon failure
|
||||
*/
|
||||
Connection.prototype.importJailedScript = function(path, sCb, fCb) {
|
||||
var f = function(){};
|
||||
this._importCallbacks[path] = {sCb: sCb||f, fCb: fCb||f};
|
||||
this._platformConnection.send({type: 'importJailed', url: path});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the code to the plugin site in order to have it executed
|
||||
* in the JAILED enviroment. Assuming the execution may only be
|
||||
* requested once by the Plugin object, which means a single set
|
||||
* of callbacks is enough (unlike importing additional scripts)
|
||||
*
|
||||
* @param {String} code code to execute
|
||||
* @param {Function} sCb to call upon success
|
||||
* @param {Function} fCb to call upon failure
|
||||
*/
|
||||
Connection.prototype.execute = function(code, sCb, fCb) {
|
||||
this._executeSCb = sCb||function(){};
|
||||
this._executeFCb = fCb||function(){};
|
||||
this._platformConnection.send({type: 'execute', code: code});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for a message received from the plugin site
|
||||
*
|
||||
* @param {Function} handler to call upon a message
|
||||
*/
|
||||
Connection.prototype.onMessage = function(handler) {
|
||||
this._messageHandler = handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a handler for a disconnect message received from the
|
||||
* plugin site
|
||||
*
|
||||
* @param {Function} handler to call upon disconnect
|
||||
*/
|
||||
Connection.prototype.onDisconnect = function(handler) {
|
||||
this._platformConnection.onDisconnect(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message to the plugin
|
||||
*
|
||||
* @param {Object} data of the message to send
|
||||
*/
|
||||
Connection.prototype.send = function(data) {
|
||||
this._platformConnection.send({
|
||||
type: 'message',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles import succeeded message from the plugin
|
||||
*
|
||||
* @param {String} url of a script loaded by the plugin
|
||||
*/
|
||||
Connection.prototype._handleImportSuccess = function(url) {
|
||||
var sCb = this._importCallbacks[url].sCb;
|
||||
this._importCallbacks[url] = null;
|
||||
delete this._importCallbacks[url];
|
||||
sCb();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles import failure message from the plugin
|
||||
*
|
||||
* @param {String} url of a script loaded by the plugin
|
||||
*/
|
||||
Connection.prototype._handleImportFailure = function(url) {
|
||||
var fCb = this._importCallbacks[url].fCb;
|
||||
this._importCallbacks[url] = null;
|
||||
delete this._importCallbacks[url];
|
||||
fCb();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the plugin when it is not needed anymore
|
||||
*/
|
||||
Connection.prototype.disconnect = function() {
|
||||
this._platformConnection.disconnect();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugin constructor, represents a plugin initialized by a script
|
||||
* with the given path
|
||||
*
|
||||
* @param {String} url of a plugin source
|
||||
* @param {Object} _interface to provide for the plugin
|
||||
*/
|
||||
var Plugin = function(url, _interface) {
|
||||
this._path = url;
|
||||
this._initialInterface = _interface||{};
|
||||
this._connect();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DynamicPlugin constructor, represents a plugin initialized by a
|
||||
* string containing the code to be executed
|
||||
*
|
||||
* @param {String} code of the plugin
|
||||
* @param {Object} _interface to provide to the plugin
|
||||
*/
|
||||
var DynamicPlugin = function(code, _interface) {
|
||||
this._code = code;
|
||||
this._initialInterface = _interface||{};
|
||||
this._connect();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the connection to the plugin site
|
||||
*/
|
||||
DynamicPlugin.prototype._connect =
|
||||
Plugin.prototype._connect = function() {
|
||||
this.remote = null;
|
||||
|
||||
this._connect = new Whenable;
|
||||
this._fail = new Whenable;
|
||||
this._disconnect = new Whenable;
|
||||
|
||||
var me = this;
|
||||
|
||||
// binded failure callback
|
||||
this._fCb = function(){
|
||||
me._fail.emit();
|
||||
me.disconnect();
|
||||
}
|
||||
|
||||
this._connection = new Connection;
|
||||
this._connection.whenInit(function(){
|
||||
me._init();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the Site object for the plugin, and then loads the
|
||||
* common routines (_JailedSite.js)
|
||||
*/
|
||||
DynamicPlugin.prototype._init =
|
||||
Plugin.prototype._init = function() {
|
||||
this._site = new JailedSite(this._connection);
|
||||
|
||||
var me = this;
|
||||
this._site.onDisconnect(function() {
|
||||
me._disconnect.emit();
|
||||
});
|
||||
|
||||
var sCb = function() {
|
||||
me._loadCore();
|
||||
}
|
||||
|
||||
this._connection.importScript(
|
||||
__jailed__path__+'_JailedSite.js', sCb, this._fCb
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the core scirpt into the plugin
|
||||
*/
|
||||
DynamicPlugin.prototype._loadCore =
|
||||
Plugin.prototype._loadCore = function() {
|
||||
var me = this;
|
||||
var sCb = function() {
|
||||
me._sendInterface();
|
||||
}
|
||||
|
||||
this._connection.importScript(
|
||||
__jailed__path__+'_pluginCore.js', sCb, this._fCb
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends to the remote site a signature of the interface provided
|
||||
* upon the Plugin creation
|
||||
*/
|
||||
DynamicPlugin.prototype._sendInterface =
|
||||
Plugin.prototype._sendInterface = function() {
|
||||
var me = this;
|
||||
this._site.onInterfaceSetAsRemote(function() {
|
||||
if (!me._connected) {
|
||||
me._loadPlugin();
|
||||
}
|
||||
});
|
||||
|
||||
this._site.setInterface(this._initialInterface);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the plugin body (loads the plugin url in case of the
|
||||
* Plugin)
|
||||
*/
|
||||
Plugin.prototype._loadPlugin = function() {
|
||||
var me = this;
|
||||
var sCb = function() {
|
||||
me._requestRemote();
|
||||
}
|
||||
|
||||
this._connection.importJailedScript(this._path, sCb, this._fCb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the plugin body (executes the code in case of the
|
||||
* DynamicPlugin)
|
||||
*/
|
||||
DynamicPlugin.prototype._loadPlugin = function() {
|
||||
var me = this;
|
||||
var sCb = function() {
|
||||
me._requestRemote();
|
||||
}
|
||||
|
||||
this._connection.execute(this._code, sCb, this._fCb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Requests the remote interface from the plugin (which was
|
||||
* probably set by the plugin during its initialization), emits
|
||||
* the connect event when done, then the plugin is fully usable
|
||||
* (meaning both the plugin and the application can use the
|
||||
* interfaces provided to each other)
|
||||
*/
|
||||
DynamicPlugin.prototype._requestRemote =
|
||||
Plugin.prototype._requestRemote = function() {
|
||||
var me = this;
|
||||
this._site.onRemoteUpdate(function(){
|
||||
me.remote = me._site.getRemote();
|
||||
me._connect.emit();
|
||||
});
|
||||
|
||||
this._site.requestRemote();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the plugin immideately
|
||||
*/
|
||||
DynamicPlugin.prototype.disconnect =
|
||||
Plugin.prototype.disconnect = function() {
|
||||
this._connection.disconnect();
|
||||
this._disconnect.emit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the provided function as a handler for the connection
|
||||
* failure Whenable event
|
||||
*
|
||||
* @param {Function} handler to be issued upon disconnect
|
||||
*/
|
||||
DynamicPlugin.prototype.whenFailed =
|
||||
Plugin.prototype.whenFailed = function(handler) {
|
||||
this._fail.whenEmitted(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the provided function as a handler for the connection
|
||||
* success Whenable event
|
||||
*
|
||||
* @param {Function} handler to be issued upon connection
|
||||
*/
|
||||
DynamicPlugin.prototype.whenConnected =
|
||||
Plugin.prototype.whenConnected = function(handler) {
|
||||
this._connect.whenEmitted(handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves the provided function as a handler for the connection
|
||||
* failure Whenable event
|
||||
*
|
||||
* @param {Function} handler to be issued upon connection failure
|
||||
*/
|
||||
DynamicPlugin.prototype.whenDisconnected =
|
||||
Plugin.prototype.whenDisconnected = function(handler) {
|
||||
this._disconnect.whenEmitted(handler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
exports.Plugin = Plugin;
|
||||
exports.DynamicPlugin = DynamicPlugin;
|
||||
|
||||
}));
|
||||
|
@ -10,6 +10,8 @@ block content
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/addon/lint/lint.css')
|
||||
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
|
||||
script(src='/js/lib/codemirror/mode/javascript/javascript.js')
|
||||
script(src='js/lib/jailed/jailed.js')
|
||||
script(src='/js/lib/bonfire/bonfire.js')
|
||||
|
||||
.row
|
||||
.col-sm-12.col-md-8.col-xs-12
|
||||
@ -24,6 +26,7 @@ block content
|
||||
textarea#codeOutput
|
||||
#submitButton.btn.btn-primary.btn-big.btn-block Run my code
|
||||
#hintButton.btn.btn-info.btn-big.btn-block Show me hints
|
||||
|
||||
script.
|
||||
var widgets = [];
|
||||
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
||||
@ -33,22 +36,12 @@ block content
|
||||
runnable: true,
|
||||
autoCloseBrackets: true,
|
||||
gutters: ["CodeMirror-lint-markers"],
|
||||
onKeyEvent : doLinting()
|
||||
onKeyEvent : doLinting
|
||||
});
|
||||
var editor = myCodeMirror;
|
||||
myCodeMirror.setValue('2*2');
|
||||
myCodeMirror.setSize("100%", 500);
|
||||
$('#submitButton').on('click', function () {
|
||||
$('#codeOutput').empty();
|
||||
var js = myCodeMirror.getValue();
|
||||
var s = document.createElement('script');
|
||||
s.textContent = js;
|
||||
try {
|
||||
$('#codeOutput').append(eval(s.textContent));
|
||||
} catch (e) {
|
||||
$('#codeOutput').append(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
|
||||
lineNumbers: false,
|
||||
@ -56,7 +49,7 @@ block content
|
||||
theme: 'monokai',
|
||||
readOnly: 'nocursor'
|
||||
});
|
||||
codeOutput.setSize("100%", 30);
|
||||
codeOutput.setSize("100%", 100);
|
||||
|
||||
var info = editor.getScrollInfo();
|
||||
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
|
||||
@ -66,8 +59,8 @@ block content
|
||||
editor.operation(function () {
|
||||
for (var i = 0; i < widgets.length; ++i)
|
||||
editor.removeLineWidget(widgets[i]);
|
||||
widgets.length = 0;
|
||||
JSHINT(editor.getValue());
|
||||
widgets.length = 0;
|
||||
JSHINT(editor.getValue());
|
||||
for (var i = 0; i < JSHINT.errors.length; ++i) {
|
||||
var err = JSHINT.errors[i];
|
||||
if (!err) continue;
|
||||
@ -78,12 +71,21 @@ block content
|
||||
msg.appendChild(document.createTextNode(err.reason));
|
||||
msg.className = "lint-error";
|
||||
widgets.push(editor.addLineWidget(err.line - 1, msg, {
|
||||
coverGutter: false,
|
||||
noHScroll: true
|
||||
coverGutter: false,
|
||||
noHScroll: true
|
||||
}));
|
||||
}
|
||||
});
|
||||
};
|
||||
$('#submitButton').on('click', function () {
|
||||
$('#codeOutput').empty();
|
||||
var js = myCodeMirror.getValue();
|
||||
submit(js);
|
||||
console.log('submitted');
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
.col-sm-12.col-md-4.col-xs-12
|
||||
include ../partials/challenges
|
||||
|
Reference in New Issue
Block a user