Skip to content

Commit 1233885

Browse files
committed
first commit
0 parents  commit 1233885

File tree

16 files changed

+3348
-0
lines changed

16 files changed

+3348
-0
lines changed

.eslintrc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"rules": {
3+
"indent": [ 2, 4 ],
4+
"quotes": [ 2, "single" ],
5+
"linebreak-style": [ 2, "unix" ],
6+
"semi": [ 2, "always" ],
7+
"no-unused-vars": [ 2, {
8+
"vars": "all",
9+
"args": "none"
10+
} ],
11+
"spaced-comment": [ 2, "always" ]
12+
},
13+
"env": {
14+
"node": true,
15+
"mocha": true
16+
},
17+
"extends": "eslint:recommended"
18+
}

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Logs
2+
logs
3+
*.log
4+
5+
# Runtime data
6+
pids
7+
*.pid
8+
*.seed
9+
10+
# Directory for instrumented libs generated by jscoverage/JSCover
11+
lib-cov
12+
13+
# Coverage directory used by tools like istanbul
14+
coverage
15+
16+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17+
.grunt
18+
19+
# node-waf configuration
20+
.lock-wscript
21+
22+
# Compiled binary addons (http://nodejs.org/api/addons.html)
23+
build/Release
24+
25+
# Dependency directory
26+
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27+
node_modules
28+
29+
.env
30+
31+
docs/_book/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# electron-app-releases

bin/web.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
var express = require('express');
2+
var uuid = require('uuid');
3+
var basicAuth = require('basic-auth');
4+
var Analytics = require('analytics-node');
5+
var nuts = require('../');
6+
var dotenv = require('dotenv');
7+
8+
dotenv.config();
9+
10+
var app = express();
11+
12+
var apiAuth = {
13+
username: process.env.API_USERNAME,
14+
password: process.env.API_PASSWORD
15+
};
16+
17+
var analytics = undefined;
18+
var downloadEvent = process.env.ANALYTICS_EVENT_DOWNLOAD || 'download';
19+
if (process.env.ANALYTICS_TOKEN) {
20+
analytics = new Analytics(process.env.ANALYTICS_TOKEN);
21+
}
22+
23+
var myNuts = nuts.Nuts({
24+
repository: process.env.GITHUB_REPO,
25+
token: process.env.GITHUB_TOKEN,
26+
endpoint: process.env.GITHUB_ENDPOINT,
27+
username: process.env.GITHUB_USERNAME,
28+
password: process.env.GITHUB_PASSWORD,
29+
timeout: process.env.VERSIONS_TIMEOUT,
30+
cache: process.env.VERSIONS_CACHE,
31+
refreshSecret: process.env.GITHUB_SECRET,
32+
proxyAssets: !Boolean(process.env.DONT_PROXY_ASSETS)
33+
});
34+
35+
// Control access to API
36+
myNuts.before('api', function(access, next) {
37+
if (!apiAuth.username) return next();
38+
39+
function unauthorized() {
40+
next(new Error('Invalid username/password for API'));
41+
};
42+
43+
var user = basicAuth(access.req);
44+
if (!user || !user.name || !user.pass) {
45+
return unauthorized();
46+
};
47+
48+
if (user.name === apiAuth.username && user.pass === apiAuth.password) {
49+
return next();
50+
} else {
51+
return unauthorized();
52+
};
53+
});
54+
55+
// Log download
56+
myNuts.before('download', function(download, next) {
57+
console.log('download', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type);
58+
59+
next();
60+
});
61+
myNuts.after('download', function(download, next) {
62+
console.log('downloaded', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type);
63+
64+
// Track on segment if enabled
65+
if (analytics) {
66+
var userId = download.req.query.user;
67+
68+
analytics.track({
69+
event: downloadEvent,
70+
anonymousId: userId? null : uuid.v4(),
71+
userId: userId,
72+
properties: {
73+
version: download.version.tag,
74+
channel: download.version.channel,
75+
platform: download.platform.type,
76+
os: nuts.platforms.toType(download.platform.type)
77+
}
78+
});
79+
}
80+
81+
next();
82+
});
83+
84+
if (process.env.TRUST_PROXY) {
85+
try {
86+
var trustProxyObject = JSON.parse(process.env.TRUST_PROXY);
87+
app.set('trust proxy', trustProxyObject);
88+
}
89+
catch (e) {
90+
app.set('trust proxy', process.env.TRUST_PROXY);
91+
}
92+
}
93+
94+
app.use(myNuts.router);
95+
96+
// Error handling
97+
app.use(function(req, res, next) {
98+
res.status(404).send("Page not found");
99+
});
100+
app.use(function(err, req, res, next) {
101+
var msg = err.message || err;
102+
var code = 500;
103+
104+
console.error(err.stack || err);
105+
106+
// Return error
107+
res.format({
108+
'text/plain': function(){
109+
res.status(code).send(msg);
110+
},
111+
'text/html': function () {
112+
res.status(code).send(msg);
113+
},
114+
'application/json': function (){
115+
res.status(code).send({
116+
'error': msg,
117+
'code': code
118+
});
119+
}
120+
});
121+
});
122+
123+
myNuts.init()
124+
125+
// Start the HTTP server
126+
.then(function() {
127+
var server = app.listen(process.env.PORT || 5000, function () {
128+
var host = server.address().address;
129+
var port = server.address().port;
130+
131+
console.log('Listening at http://%s:%s', host, port);
132+
});
133+
}, function(err) {
134+
console.log(err.stack || err);
135+
process.exit(1);
136+
});

lib/api.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
var startTime = Date.now();
2+
var Q = require('q');
3+
4+
module.exports = {
5+
'status': function () {
6+
return {
7+
uptime: (Date.now() - startTime)/1000
8+
};
9+
},
10+
11+
'versions': function (req) {
12+
return this.versions.filter({
13+
platform: req.query.platform,
14+
channel: req.query.channel || '*'
15+
});
16+
},
17+
18+
'channels': function () {
19+
return this.versions.channels();
20+
},
21+
22+
'refresh': function () {
23+
return Q()
24+
.then(this.backend.onRelease)
25+
.thenResolve({done: true}
26+
);
27+
},
28+
29+
'version/:tag': function (req) {
30+
return this.versions.resolve({
31+
tag: req.params.tag,
32+
channel: '*'
33+
});
34+
},
35+
36+
'resolve': function(req) {
37+
return this.versions.resolve({
38+
channel: req.query.channel,
39+
platform: req.query.platform,
40+
tag: req.query.tag
41+
});
42+
}
43+
};
44+

lib/backends/backend.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
var _ = require('lodash');
2+
var Q = require('q');
3+
var path = require('path');
4+
var os = require('os');
5+
var destroy = require('destroy');
6+
var LRU = require('lru-diskcache');
7+
var streamRes = require('stream-res');
8+
var Buffer = require('buffer').Buffer;
9+
10+
function Backend(nuts, opts) {
11+
this.cacheId = 0;
12+
this.nuts = nuts;
13+
this.opts = _.defaults(opts || {}, {
14+
// Folder to cache assets
15+
cache: path.resolve(os.tmpdir(), 'nuts'),
16+
17+
// Cache configuration
18+
cacheMax: 500 * 1024 * 1024,
19+
cacheMaxAge: 60 * 60 * 1000,
20+
});
21+
22+
// Create cache
23+
this.cache = LRU(opts.cache, {
24+
max: opts.cacheMax,
25+
maxAge: opts.cacheMaxAge
26+
});
27+
28+
_.bindAll(this);
29+
}
30+
31+
// Memoize a function
32+
Backend.prototype.memoize = function(fn) {
33+
var that = this;
34+
35+
return _.memoize(fn, function() {
36+
return that.cacheId+Math.ceil(Date.now()/that.opts.cacheMaxAge)
37+
});
38+
};
39+
40+
// New release? clear cache
41+
Backend.prototype.onRelease = function() {
42+
this.cacheId++;
43+
};
44+
45+
// Initialize the backend
46+
Backend.prototype.init = function() {
47+
this.cache.init();
48+
return Q();
49+
};
50+
51+
// List all releases for this repository
52+
Backend.prototype.releases = function() {
53+
54+
};
55+
56+
// Return stream for an asset
57+
Backend.prototype.serveAsset = function(asset, req, res) {
58+
var that = this;
59+
var cacheKey = asset.id;
60+
61+
function outputStream(stream) {
62+
var d = Q.defer();
63+
streamRes(res, stream, d.makeNodeResolver());
64+
return d.promise;
65+
}
66+
67+
res.header('Content-Length', asset.size);
68+
res.attachment(asset.filename);
69+
70+
// Key exists
71+
if (that.cache.has(cacheKey)) {
72+
return that.cache.getStream(cacheKey)
73+
.then(outputStream);
74+
}
75+
76+
return that.getAssetStream(asset)
77+
.then(function(stream) {
78+
return Q.all([
79+
// Cache the stream
80+
that.cache.set(cacheKey, stream),
81+
82+
// Send the stream to the user
83+
outputStream(stream)
84+
]);
85+
});
86+
};
87+
88+
// Return stream for an asset
89+
Backend.prototype.getAssetStream = function(asset) {
90+
91+
};
92+
93+
// Return stream for an asset
94+
Backend.prototype.readAsset = function(asset) {
95+
return this.getAssetStream(asset)
96+
.then(function(res) {
97+
var d = Q.defer();
98+
var output = Buffer([]);
99+
100+
function cleanup() {
101+
destroy(res);
102+
res.removeAllListeners();
103+
}
104+
105+
res.on('data', function(buf) {
106+
output = Buffer.concat([output, buf]);
107+
})
108+
.on('error', function(err) {
109+
cleanup();
110+
d.reject(err);
111+
})
112+
.on('end', function() {
113+
cleanup();
114+
d.resolve(output);
115+
});
116+
117+
return d.promise
118+
119+
})
120+
};
121+
122+
module.exports = Backend;

0 commit comments

Comments
 (0)