Skip to content

Commit 300b893

Browse files
committed
Release 1.2.0
Improved Windows asset serving & stability Handle non-existent asset file on request (Fixes ArekSredzki#55) Extended file upload timeout (Fixes ArekSredzki#47) Use correct platform for 'osx' (Fixes ArekSredzki#31) Fixed multi-arch windows asset serving (Fixes ArekSredzki#17) (Fixes ArekSredzki#45) No longer caches dynamic update resources (Fixes ArekSredzki#46)
1 parent 948d90f commit 300b893

File tree

10 files changed

+99
-47
lines changed

10 files changed

+99
-47
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66
77
[![Electron Release Server Demo](https://j.gifs.com/wpyY1X.gif)](https://youtu.be/lvT7rfB01iA)
88

9-
_Note: Despite being advertised as release server for Electron applications, it would work for **any application using Squirrel**._
9+
_Note: Despite being advertised as a release server for Electron applications, it would work for **any application using Squirrel**._
1010

11-
If you host your project on your Github and do not need a UI for your app, then [Nuts](https://github.com/GitbookIO/nuts) is probably what you're looking for. Otherwise, you're in the same boat as I was, and you've found the right place!
11+
If you host your project on your Github **and** do not need a UI for your app, then [Nuts](https://github.com/GitbookIO/nuts) is probably what you're looking for. Otherwise, you're in the same boat as I was, and you've found the right place!
1212

1313
## Features
1414
- :sparkles: Awesome release management interface powered by [AngularJS](https://angularjs.org)
1515
- Authenticates with LDAP, easy to modify to another authentication method if needed
1616
- :sparkles: Store assets on server disk, or Amazon S3 (with minor modifications)
1717
- Use pretty much any database for persistence, thanks to [Sails](http://sailsjs.org) & [Waterline](http://waterlinejs.org)
18-
- :sparkles: Simple but powerful download urls(**NOTE:** when no assets are uploaded, server returns `404` by default):
18+
- :sparkles: Simple but powerful download urls (**NOTE:** when no assets are uploaded, server returns `404` by default):
1919
- `/download/latest`
2020
- `/download/latest/:os`
2121
- `/download/:version`
2222
- `/download/:version/:os`
2323
- `/download/channel/:channel`
2424
- `/download/channel/:channel/:os`
2525
- :sparkles: Support pre-release channels (`beta`, `alpha`, ...)
26-
- :sparkles: Auto-updates with [Squirrel](https://github.com/Squirrel):
26+
- :sparkles: Auto-updates with [Squirrel](https://github.com/Squirrel):
2727
- Update URLs provided: `/update/:platform/:version[/:channel]`
2828
- Mac uses `*.dmg` and `*.zip`
2929
- Windows uses `*.exe` and `*.nuget`

api/controllers/AssetController.js

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ module.exports = {
2525
/**
2626
* Download a release artifact
2727
*
28+
* Note: if a filename is specified, nothing but the filetype is used.
29+
* This is because Squirrel.Windows does a poor job of parsing the filename,
30+
* and so we must fake the filenames of x32 and x64 versions to be the same.
31+
*
2832
* (GET /download/channel/:channel/:platform?)
2933
* (GET /download/version/:version/:platform?)
30-
* (GET /download/:version/:filename)
34+
* (GET /download/:platform/:version/:filename)
3135
* (GET /download/:platform?)
3236
*/
3337
download: function(req, res) {
@@ -46,28 +50,30 @@ module.exports = {
4650
// Normalize filetype by prepending with period
4751
if (_.isString(filetype) && filetype[0] !== '.') {
4852
filetype = '.' + filetype;
53+
} else if (filename) {
54+
filetype = filename.substr(filename.lastIndexOf('.'));
4955
}
5056

51-
// When serving a specific file, platform is not required
52-
if (!filename) {
53-
// Detect platform from useragent
54-
if (!platforms) {
55-
platforms = PlatformService.detectFromRequest(req);
57+
// Detect platform from useragent
58+
if (!platforms) {
59+
platforms = PlatformService.detectFromRequest(req);
5660

57-
if (!platforms) {
58-
return res.serverError('No platform specified and impossible to detect one');
59-
}
61+
if (!platforms) {
62+
return res.serverError(
63+
'No platform specified and detecting one was unsuccessful.'
64+
);
6065
}
66+
} else {
67+
platforms = PlatformService.sanitize(platforms);
68+
}
6169

70+
if (!version) {
6271
channel = channel || 'stable';
63-
} else {
64-
platforms = undefined;
6572
}
6673

6774
var assetPromise = new Promise(function(resolve, reject) {
6875
var assetOptions = UtilityService.getTruthyObject({
6976
platform: platforms,
70-
name: filename,
7177
filetype: filetype
7278
});
7379

@@ -95,8 +101,8 @@ module.exports = {
95101
return resolve();
96102
}
97103

98-
// Sorting filename in ascending order prioritizes other files over
99-
// zip archives is both are available and matched.
104+
// Sorting filename in ascending order prioritizes other files
105+
// over zip archives is both are available and matched.
100106
return resolve(_.orderBy(
101107
version.assets, ['filetype', 'createdAt'], ['asc', 'desc']
102108
)[0]);
@@ -162,13 +168,17 @@ module.exports = {
162168
return res.badRequest('Invalid version provided.');
163169
}
164170

171+
// Set upload request timeout to 10 minutes
172+
req.setTimeout(10 * 60 * 1000);
173+
165174
req.file('file').upload(sails.config.files,
166175
function whenDone(err, uploadedFiles) {
167176
if (err) {
168177
return res.negotiate(err);
169178
}
170179

171-
// If an unexpected number of files were uploaded, respond with an error.
180+
// If an unexpected number of files were uploaded, respond with an
181+
// error.
172182
if (uploadedFiles.length !== 1) {
173183
return res.badRequest('No file was uploaded');
174184
}
@@ -182,7 +192,8 @@ module.exports = {
182192
var hashPromise;
183193

184194
if (fileExt === '.nupkg') {
185-
// Calculate the hash of the file, as it is necessary for windows files
195+
// Calculate the hash of the file, as it is necessary for windows
196+
// files
186197
hashPromise = AssetService.getHash(uploadedFile.fd);
187198
} else {
188199
hashPromise = Promise.resolve('');
@@ -206,8 +217,8 @@ module.exports = {
206217
// validation error is encountered, w/ validation info.
207218
if (err) return res.negotiate(err);
208219

209-
// If we have the pubsub hook, use the model class's publish method
210-
// to notify all subscribers about the created item
220+
// If we have the pubsub hook, use the model class's publish
221+
// method to notify all subscribers about the created item.
211222
if (req._sails.hooks.pubsub) {
212223
if (req.isSocket) {
213224
Asset.subscribe(req, newInstance);
@@ -231,7 +242,9 @@ module.exports = {
231242
query.populate('version');
232243
query
233244
.then(function foundRecord(record) {
234-
if (!record) return res.notFound('No record found with the specified `name`.');
245+
if (!record) return res.notFound(
246+
'No record found with the specified `name`.'
247+
);
235248

236249
// Delete the file & remove from db
237250
return Promise.join(

api/controllers/VersionController.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ module.exports = {
9999
newerVersions,
100100
function(prevNotes, newVersion) {
101101

102-
newVersion.assets = _.filter(newVersion.assets, function (asset) {
102+
newVersion.assets = _.filter(newVersion.assets, function(asset) {
103103
return asset.filetype === '.zip';
104104
});
105105

@@ -221,7 +221,9 @@ module.exports = {
221221
_.remove(newVersion.assets, function(o) {
222222
return o.filetype !== '.nupkg' || !o.hash;
223223
});
224-
return newVersion.assets.length && semver.lte(version, newVersion.name);
224+
return newVersion.assets.length && semver.lte(
225+
version, newVersion.name
226+
);
225227
});
226228

227229
if (!latestVersion) {
@@ -233,8 +235,11 @@ module.exports = {
233235

234236
// Change asset name to use full download link
235237
assets = _.map(latestVersion.assets, function(asset) {
236-
asset.name = url.resolve(sails.config.appUrl,
237-
'/download/' + latestVersion.name + '/' + asset.name);
238+
asset.name = url.resolve(
239+
sails.config.appUrl,
240+
'/download/' + asset.platform + '/' + latestVersion.name + '/' +
241+
latestVersion.channel + '/' + asset.name
242+
);
238243

239244
return asset;
240245
});

api/policies/noCache.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Sets no-cache header in response.
3+
*/
4+
module.exports = function (req, res, next) {
5+
sails.log.info("Applying disable cache policy");
6+
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
7+
res.header('Expires', '-1');
8+
res.header('Pragma', 'no-cache');
9+
next();
10+
};

api/services/AssetService.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ AssetService.serveFile = function(req, res, asset) {
2222
// Stream the file to the user
2323
var fileStream = fileAdapter.read(asset.fd)
2424
.on('error', function(err) {
25-
throw err;
25+
sails.log.error('An error occurred while accessing update asset.', err);
26+
res.serverError('An error occurred while accessing update asset.');
2627
})
2728
.on('end', function complete() {
2829
// After we have sent the file, log analytics, failures experienced at this

api/services/PlatformService.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,26 @@ PlatformService.detect = function(platformName, strictMatch) {
9494
}
9595

9696
var result = [];
97-
_.forEach(suffixes, function (suffix) {
97+
_.forEach(suffixes, function(suffix) {
9898
result.push(prefix + '_' + suffix);
9999
});
100100

101101
return result;
102102
};
103103

104+
PlatformService.sanitize = function(platforms) {
105+
var self = this;
106+
return _.map(platforms, function(platform) {
107+
switch (platform) {
108+
case self.OSX:
109+
case self.OSX_32:
110+
platform = self.OSX_64;
111+
break;
112+
default:
113+
}
114+
115+
return platform;
116+
});
117+
}
118+
104119
module.exports = PlatformService;

bower.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
"name": "electron-release-server",
33
"private": true,
44
"dependencies": {
5-
"angular": "^1.5.6",
5+
"angular": "^1.5.7",
66
"bootstrap-sass-official": "^3.3.6",
7-
"angular-animate": "^1.5.6",
8-
"angular-messages": "^1.5.6",
9-
"angular-route": "^1.5.6",
10-
"angular-sanitize": "^1.5.6",
11-
"angular-xeditable": "~0.1.12",
7+
"angular-animate": "^1.5.7",
8+
"angular-messages": "^1.5.7",
9+
"angular-route": "^1.5.7",
10+
"angular-sanitize": "^1.5.7",
11+
"angular-xeditable": "~0.2.0",
1212
"lodash": "~4.13.1",
1313
"angular-ui-notification": "~0.2.0",
1414
"angular-bootstrap": "~1.3.3",
@@ -22,10 +22,10 @@
2222
"ng-file-upload": "^12.0.4",
2323
"angular-PubSub": "angular.pubsub#*",
2424
"ng-device-detector": "^3.0.1",
25-
"compare-versions": "^2.0.1"
25+
"compare-versions": "^2.0.2"
2626
},
2727
"devDependencies": {
28-
"angular-mocks": "^1.5.6"
28+
"angular-mocks": "^1.5.7"
2929
},
3030
"appPath": "app",
3131
"overrides": {
@@ -39,5 +39,8 @@
3939
"assets/stylesheets/_bootstrap.scss"
4040
]
4141
}
42+
},
43+
"resolutions": {
44+
"angular": "^1.5.7"
4245
}
4346
}

config/policies.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ module.exports.policies = {
3131
AssetController: {
3232
create: 'authToken',
3333
update: 'authToken',
34-
delete: 'authToken'
34+
delete: 'authToken',
35+
download: 'noCache'
3536
},
3637

3738
ChannelController: {
@@ -43,6 +44,10 @@ module.exports.policies = {
4344
VersionController: {
4445
create: 'authToken',
4546
update: 'authToken',
46-
delete: 'authToken'
47+
delete: 'authToken',
48+
redirect: 'noCache',
49+
general: 'noCache',
50+
windows: 'noCache',
51+
releaseNotes: 'noCache'
4752
}
4853
};

config/routes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module.exports.routes = {
2828

2929
'GET /download/channel/:channel/:platform?': 'AssetController.download',
3030
'GET /download/version/:version/:platform?': 'AssetController.download',
31-
'GET /download/:version/:filename': 'AssetController.download',
31+
'GET /download/:platform/:version/:filename': 'AssetController.download',
3232
'GET /download/:platform?': 'AssetController.download',
3333

3434
'GET /update': 'VersionController.redirect',

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "electron-release-server",
33
"private": true,
4-
"version": "1.1.1",
4+
"version": "1.2.0",
55
"description": "A version server for hosting and serving the your electron desktop app releases.",
66
"dependencies": {
7-
"bluebird": "^3.4.0",
7+
"bluebird": "^3.4.1",
88
"bower": "^1.7.9",
99
"express-useragent": "^0.2.4",
1010
"fs-extra": "^0.30.0",
@@ -17,26 +17,26 @@
1717
"grunt-contrib-jst": "~1.0.0",
1818
"grunt-contrib-less": "1.3.0",
1919
"grunt-contrib-pug": "^1.0.0",
20-
"grunt-contrib-uglify": "~1.0.1",
20+
"grunt-contrib-uglify": "~2.0.0",
2121
"grunt-contrib-watch": "~1.0.0",
2222
"grunt-sails-linker": "~0.10.1",
2323
"grunt-sass": "^1.2.0",
2424
"grunt-sync": "~0.5.2",
2525
"grunt-wiredep": "^3.0.1",
2626
"include-all": "~0.1.6",
27-
"jsonwebtoken": "^7.0.0",
27+
"jsonwebtoken": "^7.1.6",
2828
"ldapauth-fork": "^2.5.2",
2929
"lodash": "^4.13.1",
3030
"mime": "^1.3.4",
3131
"passport": "^0.3.2",
3232
"passport-ldapauth": "^0.5.0",
33-
"pug": "^2.0.0-beta3",
33+
"pug": "^2.0.0-beta4",
3434
"rc": "~1.1.6",
3535
"sails": "~0.12.3",
36-
"sails-disk": "^0.10.9",
36+
"sails-disk": "^0.10.10",
3737
"sails-pg-session": "^1.0.1",
3838
"sails-postgresql": "^0.12.1",
39-
"semver": "^5.1.0",
39+
"semver": "^5.3.0",
4040
"skipper-disk": "^0.5.4",
4141
"strip-bom": "^3.0.0",
4242
"url": "^0.11.0"

0 commit comments

Comments
 (0)