Skip to content

Commit 86856bd

Browse files
authored
Merge pull request #9 from breejs/feat/server-sent-events
Feat/server sent events
2 parents 22abb37 + 27d81fa commit 86856bd

File tree

30 files changed

+447
-119
lines changed

30 files changed

+447
-119
lines changed

.lintstagedrc.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
module.exports = {
2-
"*.md,!test/snapshots/**/*.md,!test/**/snapshots/**/*.md,!locales/README.md": [
3-
filenames => filenames.map(filename => `remark ${filename} -qfo`),
4-
'git add'
2+
'*.md,!test/snapshots/**/*.md,!test/**/snapshots/**/*.md,!locales/README.md': [
3+
(filenames) => filenames.map((filename) => `remark ${filename} -qfo`)
54
],
6-
'package.json': ['fixpack', 'git add'],
7-
'*.js': ['xo --fix', 'git add ']
5+
'package.json': ['fixpack'],
6+
'*.js': ['xo --fix']
87
};

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
77
[![made with lad](https://img.shields.io/badge/made_with-lad-95CC28.svg)](https://lad.js.org)
88

9-
> An API for Bree.
9+
> An API for [Bree][].
1010
1111
## Table of Contents
1212

@@ -39,10 +39,11 @@ The API will start automatically when the Bree constructor is called.
3939

4040
## Options
4141

42-
| Option | Type | Description |
43-
|:------:|:------:|----------------------------------------------------------------------------------------------|
44-
| port | Number | The port the API will listen on. |
45-
| jwt | Object | Configurations for JWT. Only option is `secret` which will be the secret used to verify JWT. |
42+
| Option | Type | Description |
43+
| :------: | :------: | ---------------------------------------------------------------------------------------------- |
44+
| port | Number | The port the API will listen on. Default: `62893` |
45+
| jwt | Object | Configurations for JWT. Only option is `secret` which will be the secret used to verify JWT. |
46+
| sse | Object | Configurations for SSE. See [koa-sse][] for list of options. |
4647

4748
## API
4849

@@ -56,3 +57,11 @@ Check out the [API Docs](https://documenter.getpostman.com/view/17142435/TzzDLbN
5657
[MIT](LICENSE) © [Nick Baugh](http://niftylettuce.com/)
5758

5859
##
60+
61+
[npm]: https://www.npmjs.com/
62+
63+
[yarn]: https://yarnpkg.com/
64+
65+
[Bree]: https://jobscheduler.net/#/
66+
67+
[koa-sse]: https://github.com/yklykl530/koa-sse

api.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
// eslint-disable-next-line import/no-unassigned-import
22
require('./config/env');
33

4+
const process = require('process');
5+
46
const API = require('@ladjs/api');
57
const Graceful = require('@ladjs/graceful');
68
const ip = require('ip');
79

810
const logger = require('./helpers/logger');
911
const apiConfig = require('./config/api');
1012

11-
const api = new API(apiConfig);
13+
const api = new API(apiConfig());
1214

1315
if (!module.parent) {
1416
const graceful = new Graceful({
@@ -23,7 +25,7 @@ if (!module.parent) {
2325
if (process.send) process.send('ready');
2426
const { port } = api.server.address();
2527
logger.info(
26-
`Lad API server listening on ${port} (LAN: ${ip.address()}:${port})`
28+
`Bree API server listening on ${port} (LAN: ${ip.address()}:${port})`
2729
);
2830
} catch (error) {
2931
logger.error(error);

app/controllers/api/v1/control.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,53 @@ async function checkJobName(ctx, next) {
99
return next();
1010
}
1111

12-
async function start(ctx) {
12+
async function addJobNameToQuery(ctx, next) {
1313
const { jobName } = ctx.params;
1414

15-
ctx.bree.start(jobName);
15+
ctx.query = { name: jobName };
16+
17+
return next();
18+
}
19+
20+
async function start(ctx, next) {
21+
const { jobName } = ctx.params;
22+
23+
await ctx.bree.start(jobName);
1624

1725
ctx.body = {};
26+
27+
return next();
1828
}
1929

20-
async function stop(ctx) {
30+
async function stop(ctx, next) {
2131
const { jobName } = ctx.params;
2232

2333
await ctx.bree.stop(jobName);
2434

2535
ctx.body = {};
36+
37+
return next();
2638
}
2739

28-
async function run(ctx) {
40+
async function run(ctx, next) {
2941
const { jobName } = ctx.params;
3042

31-
ctx.bree.run(jobName);
43+
await ctx.bree.run(jobName);
3244

3345
ctx.body = {};
46+
47+
return next();
48+
}
49+
50+
async function restart(ctx, next) {
51+
const { jobName } = ctx.params;
52+
53+
await ctx.bree.stop(jobName);
54+
await ctx.bree.start(jobName);
55+
56+
ctx.body = {};
57+
58+
return next();
3459
}
3560

36-
module.exports = { checkJobName, start, stop, run };
61+
module.exports = { checkJobName, addJobNameToQuery, start, stop, run, restart };

app/controllers/api/v1/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const config = require('./config');
22
const jobs = require('./jobs');
33
const control = require('./control');
4+
const sse = require('./sse');
45

56
const test = (ctx) => {
67
ctx.body = { breeExists: Boolean(ctx.bree) };
78
};
89

9-
module.exports = { config, test, jobs, control };
10+
module.exports = { config, test, jobs, control, sse };

app/controllers/api/v1/jobs.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,13 @@ async function add(ctx) {
5656
let jobs;
5757

5858
try {
59-
jobs = bree.add(body.jobs);
59+
jobs = await bree.add(body.jobs);
6060
} catch (err) {
6161
return ctx.throw(Boom.badData(err));
6262
}
6363

6464
if (body.start) {
65-
for (const job of jobs) {
66-
bree.start(job.name);
67-
}
65+
await Promise.all(jobs.map((j) => bree.start(j.name)));
6866
}
6967

7068
ctx.body = { jobs };

app/controllers/api/v1/sse.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
async function connect(ctx) {
2+
if (ctx.sse) {
3+
// likely not the best way to do this
4+
// TODO: fork koa-sse and move this into the ping interval
5+
// runs every 60s
6+
const interval = setInterval(() => {
7+
const connected = ctx.sse.send({
8+
event: 'status',
9+
data: isActive(ctx)
10+
});
11+
12+
console.log('connected');
13+
14+
// clear the interval if the client is disconnected
15+
if (!connected) {
16+
clearInterval(interval);
17+
}
18+
}, 60_000);
19+
ctx.sse.send({
20+
event: 'status',
21+
data: isActive(ctx)
22+
});
23+
24+
// send bree events over sse
25+
for (const event of ['worker created', 'worker deleted']) {
26+
ctx.bree.on(event, (name) => {
27+
ctx.sse.send({ event, data: name });
28+
});
29+
}
30+
31+
ctx.sse.on('close', () => {
32+
ctx.logger.error('SSE closed');
33+
34+
clearInterval(interval);
35+
});
36+
}
37+
}
38+
39+
function isActive(ctx) {
40+
return Boolean(
41+
ctx.bree.workers.size > 0 ||
42+
ctx.bree.timeouts.size > 0 ||
43+
ctx.bree.intervals.size > 0
44+
);
45+
}
46+
47+
module.exports = { connect };

config/api.js

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,54 @@
11
const sharedConfig = require('@ladjs/shared-config');
22
const jwt = require('koa-jwt');
3+
const sse = require('koa-sse-stream');
34

45
const logger = require('../helpers/logger');
56
const routes = require('../routes');
67
const config = require('../config');
78

8-
module.exports = {
9-
...sharedConfig('API'),
10-
routes: routes.api,
11-
logger,
12-
hookBeforeRoutes: (app) => {
13-
app.use(jwt(config.jwt));
14-
}
9+
module.exports = (opts = {}) => {
10+
const sseMiddleware = sse({ ...config.sse, ...opts.sse });
11+
const jwtMiddleware = jwt({
12+
...config.jwt,
13+
...opts.jwt,
14+
getToken(ctx, _) {
15+
// pull token off of url if it is the sse endpoint
16+
if (ctx.url.indexOf('/v1/sse') === 0) {
17+
const splitUrl = ctx.url.split('/');
18+
19+
if (splitUrl.length === 4) {
20+
return splitUrl[3];
21+
}
22+
}
23+
24+
return null;
25+
}
26+
});
27+
28+
return {
29+
...sharedConfig('API'),
30+
port: config.port,
31+
...opts,
32+
routes: routes.api,
33+
logger,
34+
hookBeforeRoutes(app) {
35+
app.use((ctx, next) => {
36+
// return early if jwt is set to false
37+
if (!opts.jwt && typeof opts.jwt === 'boolean') {
38+
return next();
39+
}
40+
41+
return jwtMiddleware(ctx, next);
42+
});
43+
44+
app.use((ctx, next) => {
45+
// only do this on sse route
46+
if (ctx.url.indexOf('/v1/sse') === 0) {
47+
return sseMiddleware(ctx, next);
48+
}
49+
50+
return next();
51+
});
52+
}
53+
};
1554
};

config/env.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const process = require('process');
12
const path = require('path');
23

34
const test = process.env.NODE_ENV === 'test';

config/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const config = {
2020
appName: env.APP_NAME,
2121
appColor: env.APP_COLOR,
2222
twitter: env.TWITTER,
23-
port: 4000,
23+
port: 62_893,
2424

2525
// build directory
2626
buildBase: 'build',
@@ -30,6 +30,12 @@ const config = {
3030
secret: env.JWT_SECRET
3131
},
3232

33+
// sse options
34+
sse: {
35+
maxClients: 10_000,
36+
pingInterval: 60_000
37+
},
38+
3339
// store IP address
3440
// <https://github.com/ladjs/store-ip-address>
3541
storeIPAddress: {

0 commit comments

Comments
 (0)