-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathindex.js
224 lines (198 loc) · 6.12 KB
/
index.js
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
var express = require('express');
var app = express();
// request logging
var morgan = require('morgan');
app.use(morgan('short'));
// compress responses
var compression = require('compression');
app.use(compression());
// parse request bodies
var bodyParser = require('body-parser');
app.use(bodyParser.json({ type: '*/*' }));
// turn off unnecessary header
app.disable('x-powered-by');
// turn on strict routing
app.enable('strict routing');
// use the X-Forwarded-* headers
app.enable('trust proxy');
// template engine
app.set('view engine', 'garnet');
// favicon
var favicon = require('serve-favicon');
var path = require('path');
app.use(favicon(path.join(__dirname, 'static/favicon.ico')));
// generate UUIDs for sessions
var uuid = require('node-uuid');
// in-memory store of all the sessions
// the keys are the session IDs (strings)
// the values have the form: {
// id: '84dba68dcea2952c', // first 8 octets of a UUID
// lastActivity: new Date(), // used to find old sessions to vacuum
// lastKnownTime: 123, // milliseconds from the start of the video
// lastKnownTimeUpdatedAt: new Date(), // when we last received a time update
// state: 'playing' | 'paused', // whether the video is playing or paused
// videoId: 123 // Netflix id the video
// }
var sessions = {};
// vacuum old sessions
setInterval(function() {
console.log('Vacuuming old sessions...');
var oldSessionIds = [];
for (var sessionId in sessions) {
if (sessions.hasOwnProperty(sessionId)) {
var expiresAt = new Date();
expiresAt.setTime(sessions[sessionId].lastActivity.getTime() + 1000 * 60 * 60);
if (expiresAt < new Date()) {
oldSessionIds.push(sessionId);
}
}
}
for (var i = 0; i < oldSessionIds.length; i++) {
console.log('Deleting session ' + oldSessionIds[i] + '...');
delete sessions[oldSessionIds[i]];
}
console.log('Done vacuuming.')
console.log('Total sessions: ' + String(Object.keys(sessions).length));
}, 1000 * 60 * 60);
// enforce HTTPS in production
if (process.env.NODE_ENV === 'production') {
app.use(function(req, res, next) {
var protocol = req.protocol.toLowerCase();
// check for protocol from CloudFlare
if (req.headers['Cf-Visitor'] || req.headers['cf-visitor']) {
var visitor = JSON.parse(req.headers['Cf-Visitor'] || req.headers['cf-visitor']);
if (visitor['scheme']) {
protocol = visitor['scheme'].toLowerCase();
}
}
if (protocol !== 'https') {
res.redirect(301, 'https://' + req.hostname + req.url);
return;
}
next();
});
}
// add CORS headers
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});
// landing page
app.get('/', function(req, res) {
res.render('index.garnet');
});
// POST /sessions/create
// request {
// videoId: 123
// }
// response {
// id: '84dba68dcea2952c',
// lastActivity: new Date(),
// lastKnownTime: 123,
// lastKnownTimeUpdatedAt: new Date(),
// state: 'playing' | 'paused',
// videoId: 123
// }
app.post('/sessions/create', function(req, res) {
// validate the input
if (typeof req.body.videoId === 'undefined') {
res.status(500).send('Missing parameter: videoId');
return;
}
if (typeof req.body.videoId !== 'number' || req.body.videoId % 1 !== 0) {
res.status(500).send('Invalid parameter: videoId');
return;
}
// create the session
var now = new Date();
var session = {
id: uuid.v4().replace(/-/g, '').substr(16),
lastActivity: now,
lastKnownTime: 0,
lastKnownTimeUpdatedAt: now,
state: 'paused',
videoId: req.body.videoId
};
sessions[session.id] = session;
// response
res.json(session);
});
// POST /sessions/:id/update
// request {
// lastKnownTime: 123,
// state: 'playing' | 'paused'
// }
// response {
// id: '84dba68dcea2952c',
// lastActivity: new Date(),
// lastKnownTime: 123,
// lastKnownTimeUpdatedAt: new Date(),
// state: 'playing' | 'paused',
// videoId: 123
// }
app.post('/sessions/:id/update', function(req, res) {
// validate the input
var sessionId = req.params.id;
if (!sessions.hasOwnProperty(sessionId)) {
res.status(404).send('Unknown session id: ' + sessionId);
return;
}
if (typeof req.body.lastKnownTime === 'undefined') {
res.status(500).send('Missing parameter: lastKnownTime');
return;
}
if (typeof req.body.lastKnownTime !== 'number' || req.body.lastKnownTime % 1 !== 0) {
res.status(500).send('Invalid parameter: lastKnownTime');
return;
}
if (req.body.lastKnownTime < 0) {
res.status(500).send('Invalid parameter: lastKnownTime');
return;
}
if (typeof req.body.state === 'undefined') {
res.status(500).send('Missing parameter: state');
return;
}
if (typeof req.body.state !== 'string') {
res.status(500).send('Invalid parameter: state');
return;
}
if (req.body.state !== 'playing' && req.body.state !== 'paused') {
res.status(500).send('Invalid parameter: state');
return;
}
// update the session
var now = new Date();
sessions[sessionId].lastActivity = now;
sessions[sessionId].lastKnownTime = req.body.lastKnownTime;
sessions[sessionId].lastKnownTimeUpdatedAt = now;
sessions[sessionId].state = req.body.state;
// response
res.json(sessions[sessionId]);
});
// GET /sessions/:id
// response {
// id: '84dba68dcea2952c',
// lastActivity: new Date(),
// lastKnownTime: 123,
// lastKnownTimeUpdatedAt: new Date(),
// state: 'playing' | 'paused',
// videoId: 123
// }
app.get('/sessions/:id', function(req, res) {
// validate the input
var sessionId = req.params.id;
if (!sessions.hasOwnProperty(sessionId)) {
res.status(404).send('Unknown session id: ' + sessionId);
return;
}
// pet the watchdog
sessions[sessionId].lastActivity = new Date();
// response
res.json(sessions[sessionId]);
});
var server = app.listen(process.env.PORT || 3000, function() {
console.log('Listening on port %d.', server.address().port);
});