ldn-inbox-server 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env-example +3 -2
- package/README.md +2 -3
- package/bin/ldn-inbox-server.js +1 -1
- package/config/inbox_config.json +1 -0
- package/handler/notification_handler/multi.js +49 -8
- package/lib/handler.js +12 -2
- package/lib/index.js +113 -12
- package/package.json +2 -2
package/.env-example
CHANGED
|
@@ -4,12 +4,13 @@ LDN_SERVER_PORT=8000
|
|
|
4
4
|
LDN_SERVER_INBOX_GLOB="^.*\.jsonld$"
|
|
5
5
|
LDN_SERVER_BASEURL=http://localhost:8000
|
|
6
6
|
LDN_SERVER_INBOX_BATH_SIZE=5
|
|
7
|
+
LDN_SERVER_LOCKDIR=.lockdir
|
|
7
8
|
LDN_SERVER_INBOX_URL=inbox/
|
|
8
9
|
LDN_SERVER_INBOX_PATH=./inbox
|
|
9
10
|
LDN_SERVER_ERROR_PATH=./error
|
|
10
11
|
LDN_SERVER_OUTBOX_PATH=./outbox
|
|
11
12
|
LDN_SERVER_PUBLIC_PATH=./public
|
|
12
|
-
LDN_SERVER_JSON_SCHEMA=./config/notification_schema.json
|
|
13
|
+
#LDN_SERVER_JSON_SCHEMA=./config/notification_schema.json
|
|
13
14
|
LDN_SERVER_INBOX_CONFIG=./config/inbox_config.json
|
|
14
15
|
LDN_SERVER_OUTBOX_CONFIG=./config/outbox_config.json
|
|
15
|
-
LDN_SERVER_HAS_PUBLIC_INBOX=
|
|
16
|
+
LDN_SERVER_HAS_PUBLIC_INBOX=1
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ldn-inbox-server
|
|
2
2
|
|
|
3
|
-
An experimental LDN inbox server for [Event Notification](https://www.eventnotifications.net) messages.
|
|
3
|
+
An experimental [LDN](https://www.w3.org/TR/ldn/) inbox server for [Event Notification](https://www.eventnotifications.net) messages.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -16,8 +16,6 @@ Create required directories
|
|
|
16
16
|
mkdir config inbox public
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Copy an example JSON Schema as `config/notification_schema.json` from this project.
|
|
20
|
-
|
|
21
19
|
Start the server:
|
|
22
20
|
|
|
23
21
|
```
|
|
@@ -57,6 +55,7 @@ npx ldn-inbox-server handler @outbox -hn ./handler/send_notification_handler.js
|
|
|
57
55
|
- `LDN_SERVER_INBOX_GLOB` : glob of files to process in inbox directory
|
|
58
56
|
- `LDN_SERVER_HAS_PUBLIC_INBOX` : if true, then public read access is allowed on the inbox
|
|
59
57
|
- `LDN_SERVER_HAS_WRITABLE_INBOX` : if true, then public write access is allowed on the inbox
|
|
58
|
+
- `LDN_SERVER_LOCKDIR` : directory to store optional lock files for handler
|
|
60
59
|
|
|
61
60
|
## Multiple inboxes
|
|
62
61
|
|
package/bin/ldn-inbox-server.js
CHANGED
|
@@ -18,7 +18,7 @@ const PUBLIC_PATH = process.env.LDN_SERVER_PUBLIC_PATH ?? './public';
|
|
|
18
18
|
const INBOX_PATH = process.env.LDN_SERVER_INBOX_PATH ?? './inbox';
|
|
19
19
|
const ERROR_PATH = process.env.LDN_SERVER_ERROR_PATH ?? './error';
|
|
20
20
|
const OUTBOX_PATH = process.env.LDN_SERVER_OUTBOX_PATH ?? './outbox';
|
|
21
|
-
const JSON_SCHEMA_PATH = process.env.LDN_SERVER_JSON_SCHEMA
|
|
21
|
+
const JSON_SCHEMA_PATH = process.env.LDN_SERVER_JSON_SCHEMA;
|
|
22
22
|
const INBOX_CONFIG = process.env.LDN_SERVER_INBOX_CONFIG;
|
|
23
23
|
const OUTBOX_CONFIG = process.env.LDN_SERVER_OUTBOX_CONFIG;
|
|
24
24
|
const HAS_PUBLIC = process.env.LDN_SERVER_HAS_PUBLIC_INBOX ?? 0;
|
package/config/inbox_config.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const { dynamic_handler , parseConfig , parseAsJSON } = require('../../lib/util.js');
|
|
2
2
|
const logger = require('../../lib/util.js').getLogger();
|
|
3
|
+
const lockfile = require('proper-lockfile');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const fsPath = require('path');
|
|
6
|
+
const md5 = require('md5');
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Demonstration notification handler that start multiple notification handlers.
|
|
@@ -8,20 +12,21 @@ const logger = require('../../lib/util.js').getLogger();
|
|
|
8
12
|
* which is an array of arrays. The outer array defines independent 'workflows' that
|
|
9
13
|
* need to run on an notifiction message. The inner array defines the steps: a
|
|
10
14
|
* sequence of handlers that need to success.
|
|
15
|
+
*
|
|
16
|
+
* Optionally a configuration for a handler can contain the property `$sequential` set
|
|
17
|
+
* to true to force the sequential execution of this handler.
|
|
11
18
|
*/
|
|
12
|
-
async function handle({path,options}) {
|
|
19
|
+
async function handle({path,options,_, notification}) {
|
|
13
20
|
let success = false;
|
|
14
21
|
|
|
15
|
-
const
|
|
22
|
+
const mainConfig = parseConfig(options['config']);
|
|
16
23
|
|
|
17
|
-
if (!
|
|
24
|
+
if (! mainConfig) {
|
|
18
25
|
logger.error('no configuration found for multi_notification_handler');
|
|
19
26
|
return { path, options, success: false };
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const handlers = config['notification_handler']?.['multi']?.['handlers'];
|
|
29
|
+
const handlers = mainConfig['notification_handler']?.['multi']?.['handlers'];
|
|
25
30
|
|
|
26
31
|
if (! handlers) {
|
|
27
32
|
logger.error('no notification_handler.multi.handlers key in configuration file');
|
|
@@ -61,7 +66,9 @@ async function handle({path,options}) {
|
|
|
61
66
|
throw new Error(`failed to load ${step}`);
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
const result = await
|
|
69
|
+
const result = await maybeLock(step,config, async() => {
|
|
70
|
+
return await handler({path,options,config,notification});
|
|
71
|
+
});
|
|
65
72
|
|
|
66
73
|
if (result['break']) {
|
|
67
74
|
logger.info(`workflow[${i}] : breaks ${step} with ${result['success']}`);
|
|
@@ -130,6 +137,40 @@ async function handle({path,options}) {
|
|
|
130
137
|
logger.info(`finished multi handler`);
|
|
131
138
|
|
|
132
139
|
return { path, options, success: success };
|
|
133
|
-
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function maybeLock(step,config,callback) {
|
|
143
|
+
if (config['$sequential']) {
|
|
144
|
+
const lockDir = process.env.LDN_SERVER_LOCKDIR || '.lockdir';
|
|
145
|
+
|
|
146
|
+
if (! fs.existsSync(lockDir)) {
|
|
147
|
+
logger.debug(`creating lock dir ${lockDir}`);
|
|
148
|
+
fs.mkdirSync(lockDir);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const lockFile = fsPath.join(lockDir,md5(step));
|
|
152
|
+
|
|
153
|
+
if (! fs.existsSync(lockFile)) {
|
|
154
|
+
logger.debug(`creating lock file ${lockFile}`);
|
|
155
|
+
fs.writeFileSync(lockFile,'');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
logger.debug(`locking ${step} using ${lockFile}`);
|
|
159
|
+
|
|
160
|
+
const unlock = await lockfile.lock(lockFile);
|
|
161
|
+
|
|
162
|
+
const result = await callback();
|
|
163
|
+
|
|
164
|
+
logger.debug(`unlocking ${step} from ${lockFile}`);
|
|
165
|
+
|
|
166
|
+
unlock();
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
logger.trace(`asynchronous ${step}`);
|
|
172
|
+
return await callback();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
134
175
|
|
|
135
176
|
module.exports = { handle };
|
package/lib/handler.js
CHANGED
|
@@ -4,7 +4,8 @@ const lockfile = require('proper-lockfile');
|
|
|
4
4
|
const { dynamic_handler, moveTo } = require('../lib/util');
|
|
5
5
|
const piscina = require('piscina');
|
|
6
6
|
const chokidar = require('chokidar');
|
|
7
|
-
const logger = require('../lib/util
|
|
7
|
+
const logger = require('../lib/util').getLogger();
|
|
8
|
+
const { parseAsJSON } = require('../lib/util');
|
|
8
9
|
|
|
9
10
|
async function handle_inbox(path,options) {
|
|
10
11
|
if (options.single) {
|
|
@@ -150,8 +151,17 @@ async function inboxProcessor(pool,path,options) {
|
|
|
150
151
|
|
|
151
152
|
logger.info(`adding ${fullPath} to queue`);
|
|
152
153
|
|
|
154
|
+
const notification = parseAsJSON(fullPath);
|
|
155
|
+
|
|
153
156
|
promises.push(
|
|
154
|
-
pool.run({
|
|
157
|
+
pool.run({
|
|
158
|
+
path: fullPath,
|
|
159
|
+
options: options,
|
|
160
|
+
config: {},
|
|
161
|
+
notification: notification
|
|
162
|
+
},
|
|
163
|
+
{ name: 'handle'}
|
|
164
|
+
)
|
|
155
165
|
);
|
|
156
166
|
|
|
157
167
|
locks.push(lock);
|
package/lib/index.js
CHANGED
|
@@ -17,15 +17,18 @@ const { handle_inbox } = require('../lib/handler');
|
|
|
17
17
|
const logger = getLogger();
|
|
18
18
|
|
|
19
19
|
function doInbox(req,res,opts) {
|
|
20
|
-
if (req.method === 'GET'
|
|
20
|
+
if (req.method === 'GET') {
|
|
21
21
|
doInboxGET(req,res,opts);
|
|
22
22
|
}
|
|
23
|
-
else if (req.method == 'HEAD'
|
|
23
|
+
else if (req.method == 'HEAD') {
|
|
24
24
|
doInboxHEAD(req,res,opts);
|
|
25
25
|
}
|
|
26
26
|
else if (req.method === 'POST' && opts['inboxWriteable'] == 1) {
|
|
27
27
|
doInboxPOST(req,res,opts);
|
|
28
28
|
}
|
|
29
|
+
else if (req.method === 'OPTIONS') {
|
|
30
|
+
doInboxOPTIONS(req,res,opts);
|
|
31
|
+
}
|
|
29
32
|
else {
|
|
30
33
|
logger.error(`tried method ${req.method} on inbox : forbidden`);
|
|
31
34
|
res.writeHead(403);
|
|
@@ -50,6 +53,88 @@ function doInboxGET(req,res,opts) {
|
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
function doInboxOPTIONS(req,res,opts) {
|
|
57
|
+
const pathItem = req.url.substring(opts['url'].length);
|
|
58
|
+
const id = pathItem.substring(1);
|
|
59
|
+
|
|
60
|
+
logger.debug(`doInboxOPTIONS (for ${pathItem})`);
|
|
61
|
+
|
|
62
|
+
if (pathItem === '/') {
|
|
63
|
+
const meta = getBody(`${id}.meta`,opts);
|
|
64
|
+
|
|
65
|
+
let metadata = {};
|
|
66
|
+
|
|
67
|
+
if (meta) {
|
|
68
|
+
metadata = JSON.parse(meta);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const allow = ['GET','HEAD','OPTIONS'];
|
|
72
|
+
|
|
73
|
+
if (opts['inboxWriteable'] == 1) {
|
|
74
|
+
allow.push('POST');
|
|
75
|
+
metadata['Accept-Post'] = 'application/ld+json';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
metadata['Allow'] = allow.join(",");
|
|
79
|
+
|
|
80
|
+
logger.debug(metadata);
|
|
81
|
+
|
|
82
|
+
for (let property in metadata) {
|
|
83
|
+
if (property !== 'Content-Type') {
|
|
84
|
+
res.setHeader(property,metadata[property]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
res.writeHead(200);
|
|
89
|
+
res.end();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/)) {
|
|
94
|
+
const result = getBody(id,opts);
|
|
95
|
+
|
|
96
|
+
if (! result) {
|
|
97
|
+
res.writeHead(403);
|
|
98
|
+
res.end('Forbidden');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const meta = getBody(`${id}.meta`,opts);
|
|
103
|
+
|
|
104
|
+
let metadata = {};
|
|
105
|
+
|
|
106
|
+
if (meta) {
|
|
107
|
+
metadata = JSON.parse(meta);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const allow = ['OPTIONS'];
|
|
111
|
+
|
|
112
|
+
if (opts['inboxPublic'] == 1) {
|
|
113
|
+
allow.push('GET');
|
|
114
|
+
allow.push('HEAD');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
metadata['Allow'] = allow.join(",");
|
|
118
|
+
|
|
119
|
+
logger.debug(metadata);
|
|
120
|
+
|
|
121
|
+
for (let property in metadata) {
|
|
122
|
+
if (property !== 'Content-Type') {
|
|
123
|
+
res.setHeader(property,metadata[property]);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
res.writeHead(200);
|
|
128
|
+
res.end();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
res.writeHead(403);
|
|
133
|
+
res.end('Forbidden');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
53
138
|
function doInboxHEAD(req,res,opts) {
|
|
54
139
|
const pathItem = req.url.substring(opts['url'].length);
|
|
55
140
|
const id = pathItem.substring(1);
|
|
@@ -73,7 +158,7 @@ function doInboxHEAD(req,res,opts) {
|
|
|
73
158
|
return;
|
|
74
159
|
}
|
|
75
160
|
|
|
76
|
-
if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/)) {
|
|
161
|
+
if (pathItem.match(/^\/[A-Za-z0-9_-]+\.jsonld$/) && opts['inboxPublic'] == 1) {
|
|
77
162
|
const result = getBody(id,opts);
|
|
78
163
|
|
|
79
164
|
if (! result) {
|
|
@@ -106,9 +191,13 @@ function doInboxHEAD(req,res,opts) {
|
|
|
106
191
|
}
|
|
107
192
|
|
|
108
193
|
function doInboxGET_Index(req,res,opts) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
194
|
+
let notifications = [];
|
|
195
|
+
|
|
196
|
+
if (opts['inboxPublic'] == 1) {
|
|
197
|
+
notifications = listInbox(opts).map( (e) => {
|
|
198
|
+
return opts['base'] + '/' + opts['url'] + e;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
112
201
|
|
|
113
202
|
const result = {
|
|
114
203
|
"@context": "http://www.w3.org/ns/ldp",
|
|
@@ -137,7 +226,7 @@ function doInboxGET_Read(req,res,opts) {
|
|
|
137
226
|
const id = pathItem.substring(1);
|
|
138
227
|
const result = getBody(id,opts);
|
|
139
228
|
|
|
140
|
-
if (result) {
|
|
229
|
+
if (result && opts['inboxPublic'] == 1) {
|
|
141
230
|
const meta = getBody(`${id}.meta`,opts);
|
|
142
231
|
|
|
143
232
|
if (meta) {
|
|
@@ -192,10 +281,19 @@ function doInboxPOST(req,res,opts) {
|
|
|
192
281
|
logger.debug(postData);
|
|
193
282
|
if (checkBody(postData,opts)) {
|
|
194
283
|
const id = storeBody(postData,opts);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
284
|
+
if (opts['inboxPublic'] == 1) {
|
|
285
|
+
const message = `Accepted ${req.url}${id}`;
|
|
286
|
+
logger.debug(message);
|
|
287
|
+
res.setHeader('Location',`${req.url}${id}`);
|
|
288
|
+
res.writeHead(201);
|
|
289
|
+
res.end(message);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const message = JSON.stringify({"message":"Request accepted"});
|
|
293
|
+
logger.debug(`202 : ${message}`);
|
|
294
|
+
res.writeHead(202);
|
|
295
|
+
res.end(message);
|
|
296
|
+
}
|
|
199
297
|
}
|
|
200
298
|
else {
|
|
201
299
|
logger.error(`not-accepted post`);
|
|
@@ -254,10 +352,13 @@ function storeBody(data,opts) {
|
|
|
254
352
|
}
|
|
255
353
|
|
|
256
354
|
function checkBody(data,opts) {
|
|
257
|
-
if (!
|
|
355
|
+
if (! opts['schema'] || ! fs.existsSync(opts['schema'])) {
|
|
258
356
|
logger.debug(`no JSON_SCHEMA found`);
|
|
259
357
|
return true;
|
|
260
358
|
}
|
|
359
|
+
else {
|
|
360
|
+
logger.debug(`using schema: ${opts['schema']}`);
|
|
361
|
+
}
|
|
261
362
|
try {
|
|
262
363
|
const JSON_SCHEMA = JSON.parse(fs.readFileSync(opts['schema'], { encoding: 'utf-8'}));
|
|
263
364
|
const json = JSON.parse(data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldn-inbox-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "A demonstration Event Notifications Inbox server",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"author": "Patrick Hochstenbach <Patrick.Hochstenbach@UGent.be>",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"handle-inbox-multi": "npx ldn-inbox-server handler @inbox -hn @handler/notification_handler/multi.js",
|
|
15
15
|
"handle-outbox-multi": "npx ldn-inbox-server handler @outbox -hn @handler/notification_handler/multi.js",
|
|
16
16
|
"handle-inbox-multi-loop": "npx ldn-inbox-server handler @inbox -hn @handler/notification_handler/multi.js --loop",
|
|
17
|
-
"clean": "rm -rf error/* inbox/* outbox/* public/events/* public/events/log/* public/artifacts/artifact1 public/artifacts/artifact2 public/artifacts/*.jsonld*"
|
|
17
|
+
"real-clean": "rm -rf error/* inbox/* outbox/* public/events/* public/events/log/* public/artifacts/artifact1 public/artifacts/artifact2 public/artifacts/*.jsonld*"
|
|
18
18
|
},
|
|
19
19
|
"bin": "./bin/ldn-inbox-server.js",
|
|
20
20
|
"keywords": [
|