Skip to content

Commit 3bfa6e7

Browse files
Ben Demboskipichfl
Ben Demboski
authored andcommitted
Support (ignore) query params (#1)
Use url.parse() to just pick out the path so we don't need to worry about the actual URL format. Also, add unit tests.
1 parent 8c8b25d commit 3bfa6e7

File tree

6 files changed

+391
-40
lines changed

6 files changed

+391
-40
lines changed

index.js

+3-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
const { join } = require('path');
2-
const fs = require('fs');
1+
const createHandler = require('./lib/create-handler');
32

43
function requiredParam(param, errorMessage) {
54
if (!param) {
@@ -35,36 +34,9 @@ module.exports = function protocolServe({
3534
requiredParam(protocol, 'protocol must be specified, should be electron.protocol');
3635
requiredParam(app, 'app must be specified, should be electron.app');
3736

38-
indexPath = indexPath || join(cwd, directoryIndexFile);
39-
40-
const cache = {};
41-
const prefixLength = name.length + 3 + endpoint.length;
42-
4337
app.on('ready', () => {
44-
protocol.registerFileProtocol(name, (request, callback) => {
45-
// the request url should start with ' ${name}://${endpoint}', remove that
46-
const [url/* , hash */] = request.url.substr(prefixLength).split('#');
47-
const urlSegments = url.split('/').filter(segment => segment !== '');
48-
49-
if (urlSegments.length === 0) {
50-
urlSegments.push(directoryIndexFile);
51-
}
52-
53-
const filepath = join(cwd, ...urlSegments);
54-
55-
// redirect empty requests to index.html
56-
if (!cache[url]) {
57-
try {
58-
fs.accessSync(filepath);
59-
60-
cache[url] = filepath;
61-
} catch (err) {
62-
//
63-
}
64-
}
65-
66-
callback({ path: cache[url] || indexPath });
67-
}, error => {
38+
const options = { cwd, name, endpoint, directoryIndexFile, indexPath };
39+
protocol.registerFileProtocol(name, createHandler(options), error => {
6840
if (error) {
6941
console.error('Failed to register protocol');
7042
}

lib/handler.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const { join } = require('path');
2+
const fs = require('fs');
3+
const url = require('url');
4+
5+
module.exports = function createHandler({
6+
cwd,
7+
name,
8+
endpoint,
9+
directoryIndexFile,
10+
indexPath,
11+
}) {
12+
indexPath = indexPath || join(cwd, directoryIndexFile);
13+
const cache = {};
14+
15+
return (request, callback) => {
16+
let {
17+
host,
18+
pathname,
19+
} = url.parse(request.url);
20+
21+
pathname = pathname || '';
22+
23+
if (host !== endpoint) {
24+
callback({ error: `Unrecognized ${name}:// endpoint: '${host}'` });
25+
26+
return;
27+
}
28+
29+
const pathSegments = pathname.split('/').filter(segment => segment !== '');
30+
31+
if (pathSegments.length === 0) {
32+
pathSegments.push(directoryIndexFile);
33+
}
34+
35+
const filepath = join(cwd, ...pathSegments);
36+
37+
// Basic request caching
38+
if (!cache[url]) {
39+
try {
40+
fs.accessSync(filepath);
41+
42+
cache[url] = filepath;
43+
} catch (err) {
44+
//
45+
}
46+
}
47+
48+
// redirect unmet requests to directoryIndexFile
49+
callback({ path: cache[url] || indexPath });
50+
};
51+
};

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
"author": "Florian Pichler <pichfl@ylk.gd>",
88
"license": "MIT",
99
"scripts": {
10+
"test": "npm run mocha && npm run lint",
11+
"mocha": "mocha ./tests/**/*-test.js",
1012
"lint": "eslint ."
1113
},
1214
"devDependencies": {
13-
"eslint": "^3.17.1"
15+
"bluebird": "^3.5.0",
16+
"chai": "^3.5.0",
17+
"eslint": "^3.17.1",
18+
"mocha": "^3.2.0",
19+
"sinon": "^1.17.7"
1420
},
1521
"dependencies": {}
1622
}

tests/.eslintrc.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
root: false,
3+
env: {
4+
mocha: true,
5+
}
6+
};

tests/unit/handler-test.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const { assert } = require('chai');
2+
const sinon = require('sinon');
3+
const Promise = require('bluebird');
4+
const fs = require('fs');
5+
const { join, resolve } = require('path');
6+
const createHandler = require('../../lib/handler');
7+
8+
describe('handler', () => {
9+
let sandbox;
10+
let mockFiles;
11+
12+
before(() => {
13+
sandbox = sinon.sandbox.create();
14+
// Stub accessSync to act like only files in mockFiles exist
15+
sandbox.stub(fs, 'accessSync', path => {
16+
if (mockFiles.indexOf(path) === -1) {
17+
throw new Error('Doesn\'t exist');
18+
}
19+
});
20+
});
21+
22+
after(() => {
23+
sandbox.restore();
24+
});
25+
26+
beforeEach(() => {
27+
mockFiles = [];
28+
});
29+
30+
// Create a handler with the specified options and call it with the given URL
31+
function handlerExec(url, options = {}) {
32+
return new Promise((resolve, reject) => {
33+
let handler = createHandler(Object.assign({
34+
cwd: '.',
35+
name: 'serve',
36+
endpoint: 'dist',
37+
directoryIndexFile: 'index.html',
38+
indexPath: undefined,
39+
}, options));
40+
41+
handler({ url }, ({ path, error }) => {
42+
if (path !== undefined) {
43+
resolve(path);
44+
} else {
45+
reject(error);
46+
}
47+
});
48+
});
49+
}
50+
51+
it('works', () => {
52+
mockFiles.push('script.js');
53+
return handlerExec('serve://dist/script.js').then(path => {
54+
assert.equal(path, 'script.js');
55+
});
56+
});
57+
58+
it('serves directoryIndexFile from the root', () => {
59+
mockFiles.push('foo.html');
60+
return handlerExec('serve://dist', {
61+
directoryIndexFile: 'foo.html',
62+
}).then(path => {
63+
assert.equal(path, 'foo.html');
64+
});
65+
});
66+
67+
it('serves directoryIndexFile for missing files', () => {
68+
mockFiles.push('foo.html');
69+
return handlerExec('serve://dist/missing.js', {
70+
directoryIndexFile: 'foo.html',
71+
}).then(path => {
72+
assert.equal(path, 'foo.html');
73+
});
74+
});
75+
76+
it('respects relative cwd', () => {
77+
mockFiles.push(join('foo', 'bar', 'script.js'));
78+
return handlerExec('serve://dist/script.js', {
79+
cwd: join('foo', 'bar'),
80+
}).then(path => {
81+
assert.equal(path, join('foo', 'bar', 'script.js'));
82+
});
83+
});
84+
85+
it('respects absolute cwd', () => {
86+
mockFiles.push(resolve('foo', 'bar', 'script.js'));
87+
return handlerExec('serve://dist/script.js', {
88+
cwd: resolve('foo', 'bar'),
89+
}).then(path => {
90+
assert.equal(path, resolve('foo', 'bar', 'script.js'));
91+
});
92+
});
93+
94+
it('respects endpoint', () => {
95+
mockFiles.push('script.js');
96+
return handlerExec('serve://custom/script.js', {
97+
endpoint: 'custom',
98+
}).then(path => {
99+
assert.equal(path, 'script.js');
100+
});
101+
});
102+
103+
it('respects indexPath for missing files', () => {
104+
mockFiles.push(join('foo', 'bar.html'));
105+
return handlerExec('serve://dist/missing.js', {
106+
indexPath: join('foo', 'bar.html'),
107+
}).then(path => {
108+
assert.equal(path, join('foo', 'bar.html'));
109+
});
110+
});
111+
112+
it('ignores hashes', () => {
113+
mockFiles.push('script.js');
114+
return handlerExec('serve://dist/script.js#hash').then(path => {
115+
assert.equal(path, 'script.js');
116+
});
117+
});
118+
119+
it('ignores query params', () => {
120+
mockFiles.push('script.js');
121+
return handlerExec('serve://dist/script.js?query=param').then(path => {
122+
assert.equal(path, 'script.js');
123+
});
124+
});
125+
126+
it('errors on an unrecognized endpoint', () =>
127+
handlerExec('service://notdist/script.js').then(() => {
128+
assert.ok(false);
129+
}).catch(() => {
130+
assert.ok(true);
131+
})
132+
);
133+
});

0 commit comments

Comments
 (0)