-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.js
More file actions
198 lines (166 loc) · 5.7 KB
/
server.js
File metadata and controls
198 lines (166 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//load shelljs for NPM utilities
require('shelljs/global');
//load dotenv for configuration
require('dotenv').load({silent: true});
//dependencies
var app = require('koa')(),
router = require('koa-router')(),
staticCache = require('koa-static-cache'),
co = require('co'),
path = require("path"),
regFs = require('fs'),
intersection = require("lodash.intersection"),
union = require('lodash.union'),
fs = require('co-fs'),
lusca = require('koa-lusca'),
mongoose = require('mongoose'),
routes = require("./routes"),
transform = require("./transform"),
views = require('koa-views');
//store our models
var models = require("./models")(mongoose);
//security config
app.use(lusca({
xframe: 'SAMEORIGIN',
xssProtection: true
}));
//static file server, with gzip
app.use(staticCache(path.join(__dirname, 'public'), {
maxAge: 365 * 24 * 60 * 60,
gzip: true,
dynamic: true
}));
// Use html
app.use(views("./views", {map: {html: 'swig'}}));
// error handling
app.use(function *( next ) {
try {
yield next;
var status = this.status || 404;
if ( status === 404 ) yield this.render('not_found', {});
} catch ( err ) {
console.error(err);
err.status = err.status || 500;
// Set our response.
this.status = err.status;
yield this.render('error', {});
}
});
//middleware for busting the clientJS cache
app.use(function*( next ) {
//try our dynamic JS
try {
var jsAssets = require('./webpack-assets.json');
if ( jsAssets && jsAssets.client ) {
this.state.client = jsAssets.client.js;
}
} catch ( e ) {
this.state.client = "/javascripts/client.js"
}
//try our dynamic css
try {
var cssAssets = require('./rev-manifest.json');
if ( cssAssets && cssAssets['main.css'] ) {
this.state.css = "/stylesheets/" + cssAssets['main.css'];
}
} catch ( e ) {
this.state.css = "/stylesheets/main.css"
}
yield next;
});
//setup routes
routes(router, models);
//setup router
app
.use(router.routes())
.use(router.allowedMethods());
/**
* Event handler for the user saving their current revision
* @param socket- A reference to the socket connection
* @param data- The bin and revision data
*/
var onCodeSaved = function*( socket, data ) {
try {
if ( data.revision && data.bin && data.code ) {
//saving
var bin = yield models.bin.findOne({'id': data.bin});
//did they change the theme? persist the change
if ( bin.currentTheme !== data.currentTheme ) {
bin.currentTheme = data.currentTheme;
yield bin.save();
}
var latestRevision = yield models
.binRevision
.find({'_bin': bin._id}).sort('-hash')
.limit(1);
//create a new revision
var newRevision = new models.binRevision({
hash: latestRevision[0].hash + 1,
text: data.code,
jsResources: data.jsResources || [],
cssResources: data.cssResources || [],
state: data.state ? JSON.stringify(data.state) : null,
createdAt: new Date(),
"_bin": bin._id
});
var newResult = yield newRevision.save();
//save the result
if ( newResult ) {
socket.emit("code saved", {
bin: data.bin,
revision: newResult.hash,
jsResources: newResult.jsResources,
cssResources: newResult.cssResources,
createdAt: newResult.createdAt
});
}
}
} catch ( e ) {
console.error(e);
socket.emit("error saving", {bin: data.bin, revision: data.revision});
}
};
/**
* Event handler for code being changed on the client, and requiring transformation
* @param socket - a reference to the socket connection
* @param data- The data to transpile
*/
var onCodeChange = function*( socket, data ) {
try {
//TODO Since this is a pure function, we could memoize it for performance
var result = transform.babelTransform.transform(data.code);
socket.emit("code transformed", result.code);
} catch ( e ) {
socket.emit("code error", e.message);
}
};
//Start our Socket IO connection
//TODO this is a naive socket implementation. It doesn't currently handle
//running the server across multiple dynos or a load balancer. Need to hook up redis or a cache layer
var server = require('http').Server(app.callback()),
io = require('socket.io')(server);
io.on('connection', co.wrap(function *( socket ) {
//listen for code saves
socket.on('code save', co.wrap(onCodeSaved.bind(null, socket)));
socket.on('code change', co.wrap(onCodeChange.bind(null, socket)));
}));
//Bring up the DB
mongoose.connect(process.env.MONGOLAB_URI || process.env.MONGO_URI);
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
//ensure we have a generated folder
//this is used to store generated node_modules
var generatedFolder = path.join(__dirname, 'public/generated');
regFs.stat(generatedFolder, function( err, stats ) {
if ( err ) {
console.log("making generated folder");
regFs.mkdirSync(generatedFolder);
}
});
//start the server
var port = process.env.PORT || 3000;
server.listen(port);
//log the connection
console.log('React.run started');
});