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
|
*.iml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
bower_components
|
||||||
|
86
.jshintrc
86
.jshintrc
@ -1,48 +1,40 @@
|
|||||||
{
|
{
|
||||||
/*
|
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||||
* ENVIRONMENTS
|
"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`.
|
||||||
// Define globals exposed by Node.js.
|
"curly": false, // Require {} for every new block or scope.
|
||||||
"node": true,
|
"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.
|
||||||
* ENFORCING OPTIONS
|
"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.
|
||||||
// Force all variable names to use either camelCase style or UPPER_CASE
|
"unused": false, // Warn unused variables.
|
||||||
// with underscores.
|
"strict": false, // Require `use strict` pragma in every file.
|
||||||
"camelcase": true,
|
"trailing": true, // Prohibit trailing whitespaces.
|
||||||
|
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||||
// Prohibit use of == and != in favor of === and !==.
|
"globals": { // Globals variables.
|
||||||
"eqeqeq": true,
|
"jasmine": true,
|
||||||
|
"angular": true,
|
||||||
// Suppress warnings about == null comparisons.
|
"ApplicationConfiguration": true
|
||||||
"eqnull": true,
|
},
|
||||||
|
"predef": [ // Extra globals.
|
||||||
// Enforce tab width of 2 spaces.
|
"define",
|
||||||
"indent": 2,
|
"require",
|
||||||
|
"exports",
|
||||||
// Prohibit use of a variable before it is defined.
|
"module",
|
||||||
"latedef": true,
|
"describe",
|
||||||
|
"before",
|
||||||
// Require capitalized names for constructor functions.
|
"beforeEach",
|
||||||
"newcap": true,
|
"after",
|
||||||
|
"afterEach",
|
||||||
// Enforce use of single quotation marks for strings.
|
"it",
|
||||||
"quotmark": "single",
|
"inject",
|
||||||
|
"expect"
|
||||||
// Prohibit trailing whitespace.
|
],
|
||||||
"trailing": true,
|
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||||
|
"noempty": true // Prohibit use of empty blocks.
|
||||||
// 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
|
|
||||||
}
|
|
183
app.js
183
app.js
@ -24,8 +24,8 @@ var express = require('express'),
|
|||||||
connectAssets = require('connect-assets'),
|
connectAssets = require('connect-assets'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controllers (route handlers).
|
* Controllers (route handlers).
|
||||||
*/
|
*/
|
||||||
homeController = require('./controllers/home'),
|
homeController = require('./controllers/home'),
|
||||||
challengesController = require('./controllers/challenges'),
|
challengesController = require('./controllers/challenges'),
|
||||||
resourcesController = require('./controllers/resources'),
|
resourcesController = require('./controllers/resources'),
|
||||||
@ -34,13 +34,13 @@ var express = require('express'),
|
|||||||
bonfireController = require('./controllers/bonfire'),
|
bonfireController = require('./controllers/bonfire'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User model
|
* User model
|
||||||
*/
|
*/
|
||||||
User = require('./models/User'),
|
User = require('./models/User'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API keys and Passport configuration.
|
* API keys and Passport configuration.
|
||||||
*/
|
*/
|
||||||
secrets = require('./config/secrets'),
|
secrets = require('./config/secrets'),
|
||||||
passportConf = require('./config/passport');
|
passportConf = require('./config/passport');
|
||||||
|
|
||||||
@ -53,10 +53,10 @@ var app = express();
|
|||||||
* Connect to MongoDB.
|
* Connect to MongoDB.
|
||||||
*/
|
*/
|
||||||
mongoose.connect(secrets.db);
|
mongoose.connect(secrets.db);
|
||||||
mongoose.connection.on('error', function() {
|
mongoose.connection.on('error', function () {
|
||||||
console.error(
|
console.error(
|
||||||
'MongoDB Connection Error. Please make sure that MongoDB is running.'
|
'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.set('view engine', 'jade');
|
||||||
app.use(compress());
|
app.use(compress());
|
||||||
var oneYear = 31557600000;
|
var oneYear = 31557600000;
|
||||||
app.use(express.static(__dirname + '/public', { maxAge: oneYear }));
|
app.use(express.static(__dirname + '/public', {maxAge: oneYear}));
|
||||||
app.use(connectAssets({
|
app.use(connectAssets({
|
||||||
paths: [
|
paths: [
|
||||||
path.join(__dirname, 'public/css'),
|
path.join(__dirname, 'public/css'),
|
||||||
path.join(__dirname, 'public/js')
|
path.join(__dirname, 'public/js')
|
||||||
],
|
],
|
||||||
helperContext: app.locals
|
helperContext: app.locals
|
||||||
}));
|
}));
|
||||||
app.use(logger('dev'));
|
app.use(logger('dev'));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({extended: true}));
|
||||||
app.use(expressValidator({
|
app.use(expressValidator({
|
||||||
customValidators: {
|
customValidators: {
|
||||||
matchRegex: function(param, regex) {
|
matchRegex: function (param, regex) {
|
||||||
return regex.test(param);
|
return regex.test(param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,50 +137,50 @@ app.use(helmet.contentSecurityPolicy({
|
|||||||
defaultSrc: trusted,
|
defaultSrc: trusted,
|
||||||
scriptSrc: ['*.optimizely.com', '*.aspnetcdn.com'].concat(trusted),
|
scriptSrc: ['*.optimizely.com', '*.aspnetcdn.com'].concat(trusted),
|
||||||
'connect-src': [
|
'connect-src': [
|
||||||
'ws://*.rafflecopter.com',
|
'ws://*.rafflecopter.com',
|
||||||
'wss://*.rafflecopter.com',
|
'wss://*.rafflecopter.com',
|
||||||
'https://*.rafflecopter.com',
|
'https://*.rafflecopter.com',
|
||||||
'ws://www.freecodecamp.com',
|
'ws://www.freecodecamp.com',
|
||||||
'http://www.freecodecamp.com'
|
'http://www.freecodecamp.com'
|
||||||
].concat(trusted),
|
].concat(trusted),
|
||||||
styleSrc: trusted,
|
styleSrc: trusted,
|
||||||
imgSrc: [
|
imgSrc: [
|
||||||
'*.evernote.com',
|
'*.evernote.com',
|
||||||
'*.amazonaws.com',
|
'*.amazonaws.com',
|
||||||
'data:',
|
'data:',
|
||||||
'*.licdn.com',
|
'*.licdn.com',
|
||||||
'*.gravatar.com',
|
'*.gravatar.com',
|
||||||
'*.youtube.com',
|
'*.youtube.com',
|
||||||
'*.akamaihd.net',
|
'*.akamaihd.net',
|
||||||
'graph.facebook.com',
|
'graph.facebook.com',
|
||||||
'*.githubusercontent.com',
|
'*.githubusercontent.com',
|
||||||
'*.googleusercontent.com',
|
'*.googleusercontent.com',
|
||||||
'*' /* allow all input since we have user submitted images for public profile*/
|
'*' /* allow all input since we have user submitted images for public profile*/
|
||||||
].concat(trusted),
|
].concat(trusted),
|
||||||
fontSrc: ['*.googleapis.com'].concat(trusted),
|
fontSrc: ['*.googleapis.com'].concat(trusted),
|
||||||
mediaSrc: [
|
mediaSrc: [
|
||||||
'*.amazonaws.com',
|
'*.amazonaws.com',
|
||||||
'*.twitter.com'
|
'*.twitter.com'
|
||||||
].concat(trusted),
|
].concat(trusted),
|
||||||
frameSrc: [
|
frameSrc: [
|
||||||
'*.gitter.im',
|
'*.gitter.im',
|
||||||
'*.vimeo.com',
|
'*.vimeo.com',
|
||||||
'*.twitter.com',
|
'*.twitter.com',
|
||||||
'*.rafflecopter.com',
|
'*.rafflecopter.com',
|
||||||
'*.youtube.com'
|
'*.youtube.com'
|
||||||
].concat(trusted),
|
].concat(trusted),
|
||||||
reportOnly: false, // set to true if you only want to report errors
|
reportOnly: false, // set to true if you only want to report errors
|
||||||
setAllHeaders: false, // set to true if you want to set all headers
|
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
|
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.
|
// Make user object available in templates.
|
||||||
res.locals.user = req.user;
|
res.locals.user = req.user;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
// Remember original destination before login.
|
// Remember original destination before login.
|
||||||
var path = req.path.split('/')[1];
|
var path = req.path.split('/')[1];
|
||||||
if (/auth|login|logout|signup|fonts|favicon/i.test(path)) {
|
if (/auth|login|logout|signup|fonts|favicon/i.test(path)) {
|
||||||
@ -191,7 +191,7 @@ app.use(function(req, res, next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(
|
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('/', homeController.index);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/resources/interview-questions',
|
'/resources/interview-questions',
|
||||||
resourcesController.interviewQuestions);
|
resourcesController.interviewQuestions);
|
||||||
app.get('/learn-to-code', resourcesController.learnToCode);
|
app.get('/learn-to-code', resourcesController.learnToCode);
|
||||||
app.get('/privacy', resourcesController.privacy);
|
app.get('/privacy', resourcesController.privacy);
|
||||||
app.get('/jquery-exercises', resourcesController.jqueryExercises);
|
app.get('/jquery-exercises', resourcesController.jqueryExercises);
|
||||||
@ -215,16 +215,16 @@ app.get('/control-shortcuts', resourcesController.deployAWebsite);
|
|||||||
app.get('/stats', resourcesController.stats);
|
app.get('/stats', resourcesController.stats);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/pair-program-with-team-viewer',
|
'/pair-program-with-team-viewer',
|
||||||
resourcesController.pairProgramWithTeamViewer
|
resourcesController.pairProgramWithTeamViewer
|
||||||
);
|
);
|
||||||
app.get(
|
app.get(
|
||||||
'/done-with-first-100-hours',
|
'/done-with-first-100-hours',
|
||||||
resourcesController.doneWithFirst100Hours
|
resourcesController.doneWithFirst100Hours
|
||||||
);
|
);
|
||||||
app.get(
|
app.get(
|
||||||
'/programmer-interview-questions-app',
|
'/programmer-interview-questions-app',
|
||||||
resourcesController.programmerInterviewQuestionsApp
|
resourcesController.programmerInterviewQuestionsApp
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/about', resourcesController.about);
|
app.get('/about', resourcesController.about);
|
||||||
@ -244,9 +244,9 @@ app.post('/nonprofits', contactController.postContact);
|
|||||||
|
|
||||||
// # Protected routes, user must be logged in.
|
// # Protected routes, user must be logged in.
|
||||||
app.post(
|
app.post(
|
||||||
'/update-progress',
|
'/update-progress',
|
||||||
passportConf.isAuthenticated,
|
passportConf.isAuthenticated,
|
||||||
userController.updateProgress
|
userController.updateProgress
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
@ -274,13 +274,15 @@ app.get('/account/unlink/:provider', userController.getOauthUnlink);
|
|||||||
* and updates user.challengesHash & user.challengesCompleted
|
* 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)] =
|
req.user.challengesHash[parseInt(req.body.challengeNumber)] =
|
||||||
Math.round(+ new Date() / 1000);
|
Math.round(+new Date() / 1000);
|
||||||
var ch = req.user.challengesHash;
|
var ch = req.user.challengesHash;
|
||||||
var p = 0;
|
var p = 0;
|
||||||
for (var k in ch) {
|
for (var k in ch) {
|
||||||
if (ch[k] > 0) { p += 1; }
|
if (ch[k] > 0) {
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
req.user.points = p;
|
req.user.points = p;
|
||||||
req.user.save();
|
req.user.save();
|
||||||
@ -291,60 +293,60 @@ app.post('/completed_challenge', function(req, res) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var passportOptions = {
|
var passportOptions = {
|
||||||
successRedirect: '/',
|
successRedirect: '/',
|
||||||
failureRedirect: '/login'
|
failureRedirect: '/login'
|
||||||
};
|
};
|
||||||
|
|
||||||
app.get('/auth/twitter', passport.authenticate('twitter'));
|
app.get('/auth/twitter', passport.authenticate('twitter'));
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/twitter/callback',
|
'/auth/twitter/callback',
|
||||||
passport.authenticate('twitter', {
|
passport.authenticate('twitter', {
|
||||||
successRedirect: '/',
|
successRedirect: '/',
|
||||||
failureRedirect: '/login'
|
failureRedirect: '/login'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/linkedin',
|
'/auth/linkedin',
|
||||||
passport.authenticate('linkedin', {
|
passport.authenticate('linkedin', {
|
||||||
state: 'SOME STATE'
|
state: 'SOME STATE'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/linkedin/callback',
|
'/auth/linkedin/callback',
|
||||||
passport.authenticate('linkedin', passportOptions)
|
passport.authenticate('linkedin', passportOptions)
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/facebook',
|
'/auth/facebook',
|
||||||
passport.authenticate('facebook', { scope: ['email', 'user_location'] })
|
passport.authenticate('facebook', {scope: ['email', 'user_location']})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/facebook/callback',
|
'/auth/facebook/callback',
|
||||||
passport.authenticate('facebook', passportOptions), function(req, res) {
|
passport.authenticate('facebook', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/auth/github', passport.authenticate('github'));
|
app.get('/auth/github', passport.authenticate('github'));
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/github/callback',
|
'/auth/github/callback',
|
||||||
passport.authenticate('github', passportOptions), function(req, res) {
|
passport.authenticate('github', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/google',
|
'/auth/google',
|
||||||
passport.authenticate('google', { scope: 'profile email' })
|
passport.authenticate('google', {scope: 'profile email'})
|
||||||
);
|
);
|
||||||
app.get(
|
app.get(
|
||||||
'/auth/google/callback',
|
'/auth/google/callback',
|
||||||
passport.authenticate('google', passportOptions), function(req, res) {
|
passport.authenticate('google', passportOptions), function (req, res) {
|
||||||
res.redirect(req.session.returnTo || '/');
|
res.redirect(req.session.returnTo || '/');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -352,7 +354,6 @@ app.get(
|
|||||||
*/
|
*/
|
||||||
app.get('/bonfire', bonfireController.index);
|
app.get('/bonfire', bonfireController.index);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 500 Error Handler.
|
* 500 Error Handler.
|
||||||
@ -362,12 +363,12 @@ app.use(errorHandler());
|
|||||||
/**
|
/**
|
||||||
* Start Express server.
|
* Start Express server.
|
||||||
*/
|
*/
|
||||||
app.listen(app.get('port'), function() {
|
app.listen(app.get('port'), function () {
|
||||||
console.log(
|
console.log(
|
||||||
'FreeCodeCamp server listening on port %d in %s mode',
|
'FreeCodeCamp server listening on port %d in %s mode',
|
||||||
app.get('port'),
|
app.get('port'),
|
||||||
app.get('env')
|
app.get('env')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
@ -16,4 +16,5 @@ exports.index = function(req, res) {
|
|||||||
cc: req.user ? req.user.challengesHash : undefined
|
cc: req.user ? req.user.challengesHash : undefined
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,54 +1,40 @@
|
|||||||
{
|
{
|
||||||
/*
|
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||||
* ENVIRONMENTS
|
"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`.
|
||||||
// Define globals exposed by modern browsers.
|
"curly": false, // Require {} for every new block or scope.
|
||||||
"browser": true,
|
"eqeqeq": true, // Require triple equals i.e. `===`.
|
||||||
|
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||||
// Define globals exposed by jQuery.
|
"latedef": true, // Prohibit variable use before definition.
|
||||||
"jquery": true,
|
"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.
|
||||||
* ENFORCING OPTIONS
|
"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.
|
||||||
// Force all variable names to use either camelCase style or UPPER_CASE
|
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
|
||||||
// with underscores.
|
"globals": { // Globals variables.
|
||||||
"camelcase": true,
|
"jasmine": true,
|
||||||
|
"angular": true,
|
||||||
// Prohibit use of == and != in favor of === and !==.
|
"ApplicationConfiguration": true
|
||||||
"eqeqeq": true,
|
},
|
||||||
|
"predef": [ // Extra globals.
|
||||||
// Suppress warnings about == null comparisons.
|
"define",
|
||||||
"eqnull": true,
|
"require",
|
||||||
|
"exports",
|
||||||
// Enforce tab width of 2 spaces.
|
"module",
|
||||||
"indent": 2,
|
"describe",
|
||||||
|
"before",
|
||||||
// Prohibit use of a variable before it is defined.
|
"beforeEach",
|
||||||
"latedef": true,
|
"after",
|
||||||
|
"afterEach",
|
||||||
// Require capitalized names for constructor functions.
|
"it",
|
||||||
"newcap": true,
|
"inject",
|
||||||
|
"expect"
|
||||||
// Enforce use of single quotation marks for strings.
|
],
|
||||||
"quotmark": "single",
|
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||||
|
"noempty": true // Prohibit use of empty blocks.
|
||||||
// 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'
|
|
||||||
}
|
|
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/addon/lint/lint.css')
|
||||||
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
|
link(rel='stylesheet', href='/js/lib/codemirror/theme/monokai.css')
|
||||||
script(src='/js/lib/codemirror/mode/javascript/javascript.js')
|
script(src='/js/lib/codemirror/mode/javascript/javascript.js')
|
||||||
|
script(src='js/lib/jailed/jailed.js')
|
||||||
|
script(src='/js/lib/bonfire/bonfire.js')
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-sm-12.col-md-8.col-xs-12
|
.col-sm-12.col-md-8.col-xs-12
|
||||||
@ -24,6 +26,7 @@ block content
|
|||||||
textarea#codeOutput
|
textarea#codeOutput
|
||||||
#submitButton.btn.btn-primary.btn-big.btn-block Run my code
|
#submitButton.btn.btn-primary.btn-big.btn-block Run my code
|
||||||
#hintButton.btn.btn-info.btn-big.btn-block Show me hints
|
#hintButton.btn.btn-info.btn-big.btn-block Show me hints
|
||||||
|
|
||||||
script.
|
script.
|
||||||
var widgets = [];
|
var widgets = [];
|
||||||
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("codeEditor"), {
|
||||||
@ -33,22 +36,12 @@ block content
|
|||||||
runnable: true,
|
runnable: true,
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
gutters: ["CodeMirror-lint-markers"],
|
gutters: ["CodeMirror-lint-markers"],
|
||||||
onKeyEvent : doLinting()
|
onKeyEvent : doLinting
|
||||||
});
|
});
|
||||||
var editor = myCodeMirror;
|
var editor = myCodeMirror;
|
||||||
myCodeMirror.setValue('2*2');
|
myCodeMirror.setValue('2*2');
|
||||||
myCodeMirror.setSize("100%", 500);
|
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"), {
|
var codeOutput = CodeMirror.fromTextArea(document.getElementById("codeOutput"), {
|
||||||
lineNumbers: false,
|
lineNumbers: false,
|
||||||
@ -56,7 +49,7 @@ block content
|
|||||||
theme: 'monokai',
|
theme: 'monokai',
|
||||||
readOnly: 'nocursor'
|
readOnly: 'nocursor'
|
||||||
});
|
});
|
||||||
codeOutput.setSize("100%", 30);
|
codeOutput.setSize("100%", 100);
|
||||||
|
|
||||||
var info = editor.getScrollInfo();
|
var info = editor.getScrollInfo();
|
||||||
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
|
var after = editor.charCoords({line: editor.getCursor().line + 1, ch: 0}, "local").top;
|
||||||
@ -66,8 +59,8 @@ block content
|
|||||||
editor.operation(function () {
|
editor.operation(function () {
|
||||||
for (var i = 0; i < widgets.length; ++i)
|
for (var i = 0; i < widgets.length; ++i)
|
||||||
editor.removeLineWidget(widgets[i]);
|
editor.removeLineWidget(widgets[i]);
|
||||||
widgets.length = 0;
|
widgets.length = 0;
|
||||||
JSHINT(editor.getValue());
|
JSHINT(editor.getValue());
|
||||||
for (var i = 0; i < JSHINT.errors.length; ++i) {
|
for (var i = 0; i < JSHINT.errors.length; ++i) {
|
||||||
var err = JSHINT.errors[i];
|
var err = JSHINT.errors[i];
|
||||||
if (!err) continue;
|
if (!err) continue;
|
||||||
@ -78,12 +71,21 @@ block content
|
|||||||
msg.appendChild(document.createTextNode(err.reason));
|
msg.appendChild(document.createTextNode(err.reason));
|
||||||
msg.className = "lint-error";
|
msg.className = "lint-error";
|
||||||
widgets.push(editor.addLineWidget(err.line - 1, msg, {
|
widgets.push(editor.addLineWidget(err.line - 1, msg, {
|
||||||
coverGutter: false,
|
coverGutter: false,
|
||||||
noHScroll: true
|
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
|
.col-sm-12.col-md-4.col-xs-12
|
||||||
include ../partials/challenges
|
include ../partials/challenges
|
||||||
|
Reference in New Issue
Block a user