ldn-inbox-server 1.2.0 → 1.2.2
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 → .env-example} +1 -0
- package/README.md +11 -8
- package/bin/ldn-inbox-server.js +9 -3
- package/handler/demo_inbox_handler.js +2 -0
- package/handler/eventlog_notification_handler.js +103 -0
- package/lib/index.js +136 -3
- package/lib/sendNotificationHandler.js +30 -0
- package/package.json +2 -1
- /package/lib/{notification.js → demoNotificationHandler.js} +0 -0
- /package/public/{index.html → index.html-example} +0 -0
package/{.env → .env-example}
RENAMED
package/README.md
CHANGED
|
@@ -45,14 +45,17 @@ npx ldn-inbox-server handle @outbox
|
|
|
45
45
|
## Environment
|
|
46
46
|
|
|
47
47
|
- `LOG4JS` : log4js logging level
|
|
48
|
-
- `LDN_SERVER_HOST` :
|
|
49
|
-
- `LDN_SERVER_PORT` :
|
|
50
|
-
- `LDN_SERVER_INBOX_URL` :
|
|
51
|
-
- `LDN_SERVER_INBOX_PATH` :
|
|
52
|
-
- `LDN_SERVER_ERROR_PATH` :
|
|
53
|
-
- `LDN_SERVER_OUTBOX_PATH` :
|
|
54
|
-
- `LDN_SERVER_PUBLIC_PATH` :
|
|
55
|
-
- `LDN_SERVER_JSON_SCHEMA` :
|
|
48
|
+
- `LDN_SERVER_HOST` : LDN inbox host
|
|
49
|
+
- `LDN_SERVER_PORT` : LDN inbox port
|
|
50
|
+
- `LDN_SERVER_INBOX_URL` : LDN inbox url (path)
|
|
51
|
+
- `LDN_SERVER_INBOX_PATH` : LDN inbox path
|
|
52
|
+
- `LDN_SERVER_ERROR_PATH` : LDN error path
|
|
53
|
+
- `LDN_SERVER_OUTBOX_PATH` : LDN outbox path
|
|
54
|
+
- `LDN_SERVER_PUBLIC_PATH` : public (HTML) path
|
|
55
|
+
- `LDN_SERVER_JSON_SCHEMA` : notification JSON validation schema
|
|
56
|
+
- `LDN_SERVER_BASEURL` : baseurl of the LDN inbox server
|
|
57
|
+
- `LDN_SERVER_INBOX_GLOB` : glob of files to process in inbox directory
|
|
58
|
+
- `LDN_SERVER_HAS_PUBLIC_INBOX` : if true, then public read access is allowed on inbox
|
|
56
59
|
|
|
57
60
|
## Extend
|
|
58
61
|
|
package/bin/ldn-inbox-server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
3
4
|
const { program } = require('commander');
|
|
4
5
|
const { inbox_server } = require('../lib/index');
|
|
5
6
|
const { handle_inbox , defaultSendNotificationHandler } = require('../lib/handler');
|
|
@@ -10,26 +11,30 @@ const PORT = process.env.LDN_SERVER_PORT ?? 8000;
|
|
|
10
11
|
const INBOX_GLOB = process.env.LDN_SERVER_INBOX_GLOB ?? "^.*\\.jsonld$";
|
|
11
12
|
const INBOX_BATCH_SIZE = process.env.LDN_SERVER_INBOX_BATH_SIZE ?? 5;
|
|
12
13
|
const INBOX_URL = process.env.LDN_SERVER_INBOX_URL ?? 'inbox/';
|
|
14
|
+
const INBOX_BASE_URL = process.env.LDN_SERVER_BASEURL ?? 'http://localhost:8000';
|
|
13
15
|
const PUBLIC_PATH = process.env.LDN_SERVER_PUBLIC_PATH ?? './public';
|
|
14
16
|
const INBOX_PATH = process.env.LDN_SERVER_INBOX_PATH ?? './inbox';
|
|
15
17
|
const ERROR_PATH = process.env.LDN_SERVER_ERROR_PATH ?? './error';
|
|
16
18
|
const OUTBOX_PATH = process.env.LDN_SERVER_OUTBOX_PATH ?? './outbox';
|
|
17
19
|
const JSON_SCHEMA_PATH = process.env.LDN_SERVER_JSON_SCHEMA ?? './config/notification_schema.json';
|
|
20
|
+
const HAS_PUBLIC = process.env.LDN_SERVER_HAS_PUBLIC_INBOX ?? 0;
|
|
18
21
|
|
|
19
22
|
program
|
|
20
23
|
.name('lnd-inbox-server')
|
|
21
|
-
.version('1.2.
|
|
24
|
+
.version('1.2.2')
|
|
22
25
|
.description('A demonstration Event Notifications Inbox server');
|
|
23
26
|
|
|
24
27
|
program
|
|
25
28
|
.command('start-server')
|
|
26
29
|
.option('--host <host>','host',HOST)
|
|
27
30
|
.option('--port <port>','port',PORT)
|
|
28
|
-
.option('--url <
|
|
31
|
+
.option('--url <path>','path',INBOX_URL)
|
|
32
|
+
.option('--base <url>','base url',INBOX_BASE_URL)
|
|
29
33
|
.option('--inbox <inbox>','inbox',INBOX_PATH)
|
|
30
34
|
.option('--public <public>','public',PUBLIC_PATH)
|
|
31
35
|
.option('--schema <schema>','json schema',JSON_SCHEMA_PATH)
|
|
32
36
|
.option('--registry <registry>','registry',null)
|
|
37
|
+
.option('--inbox-public','public readable inbox',HAS_PUBLIC)
|
|
33
38
|
.action( (options) => {
|
|
34
39
|
inbox_server(options);
|
|
35
40
|
});
|
|
@@ -38,6 +43,7 @@ program
|
|
|
38
43
|
.command('handler')
|
|
39
44
|
.option('--inbox <inbox>','inbox',INBOX_PATH)
|
|
40
45
|
.option('--outbox <outbox>','outbox',OUTBOX_PATH)
|
|
46
|
+
.option('--public <public>','public',PUBLIC_PATH)
|
|
41
47
|
.option('--error <errbox>','errbox',ERROR_PATH)
|
|
42
48
|
.option('--loop <seconds>', 'run in a loop',0)
|
|
43
49
|
.option('--batch_size <num>','batch size to process',INBOX_BATCH_SIZE)
|
|
@@ -53,7 +59,7 @@ program
|
|
|
53
59
|
case '@outbox':
|
|
54
60
|
box = OUTBOX_PATH;
|
|
55
61
|
options['notification_handler'] =
|
|
56
|
-
options['notification_handler'] ??
|
|
62
|
+
options['notification_handler'] ?? path.join(__dirname,'..','lib','sendNotificationHandler.js');
|
|
57
63
|
break;
|
|
58
64
|
}
|
|
59
65
|
if (options['loop']) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const fsPath = require('path');
|
|
3
|
+
const lockfile = require('proper-lockfile');
|
|
4
|
+
const logger = require('../lib/util.js').getLogger();
|
|
5
|
+
|
|
6
|
+
const EVENT_DIR = 'events';
|
|
7
|
+
const EVENT_LOG = 'events.jsonld';
|
|
8
|
+
|
|
9
|
+
async function handle({path,options}) {
|
|
10
|
+
logger.info(`parsing notification ${path}`);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const json = JSON.parse(fs.readFileSync(path, { encoding: 'utf-8'}));
|
|
14
|
+
|
|
15
|
+
const fileName = path.split('/').pop();
|
|
16
|
+
const logDir = fsPath.join(options['public'],EVENT_DIR,'log');
|
|
17
|
+
|
|
18
|
+
if (! fs.existsSync(logDir)) {
|
|
19
|
+
logger.info(`creating ${logDir}`);
|
|
20
|
+
fs.mkdirSync(logDir, { recursive : true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const outboxFile = fsPath.join(logDir,fileName);
|
|
24
|
+
|
|
25
|
+
fs.writeFileSync(outboxFile, JSON.stringify(json));
|
|
26
|
+
|
|
27
|
+
// Updating metadata file
|
|
28
|
+
const metaFile = outboxFile + '.meta';
|
|
29
|
+
|
|
30
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
31
|
+
'Content-Type': 'application/ld+json',
|
|
32
|
+
'Last-Modified': nowISO()
|
|
33
|
+
},null,4));
|
|
34
|
+
|
|
35
|
+
await updateEventLog({path,options});
|
|
36
|
+
|
|
37
|
+
return { path,options, success: true };
|
|
38
|
+
}
|
|
39
|
+
catch(e) {
|
|
40
|
+
logger.error(`failed to process ${path}`);
|
|
41
|
+
logger.error(e);
|
|
42
|
+
return { path,options, success: false };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function updateEventLog({path,options}) {
|
|
47
|
+
logger.info(`updating eventlog for ${path}`);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const baseUrl = process.env.LDN_SERVER_BASEURL ?? "";
|
|
51
|
+
const fileName = path.split('/').pop();
|
|
52
|
+
const entry = `${baseUrl}/${EVENT_DIR}/log/${fileName}`;
|
|
53
|
+
|
|
54
|
+
const eventLog = fsPath.join(options['public'],EVENT_DIR,EVENT_LOG);
|
|
55
|
+
|
|
56
|
+
let json;
|
|
57
|
+
|
|
58
|
+
if (fs.existsSync(eventLog)) {
|
|
59
|
+
json = JSON.parse(fs.readFileSync(eventLog, { encoding: 'utf-8'}));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
json = {
|
|
63
|
+
"@context": "https://labs.eventnotifications.net/contexts/eventlog.jsonld",
|
|
64
|
+
"type": "EventLog",
|
|
65
|
+
"member": []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (json['member'].findIndex( (e) => e === entry) >= 0) {
|
|
70
|
+
logger.info(`${entry} already in ${eventLog}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logger.info(`updating ${eventLog}`);
|
|
74
|
+
|
|
75
|
+
json['member'].push(entry);
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(eventLog)) {
|
|
78
|
+
try {
|
|
79
|
+
const lock = await lockfile(eventLog, { retries: 10 });
|
|
80
|
+
fs.writeFileSync(eventLog,JSON.stringify(json,null,4));
|
|
81
|
+
lock(); // release
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
logger.error(`failed to update ${eventLog}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
fs.writeFileSync(eventLog,JSON.stringify(json,null,4));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function nowISO() {
|
|
100
|
+
return (new Date()).toUTCString();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { handle };
|
package/lib/index.js
CHANGED
|
@@ -9,16 +9,20 @@ const {
|
|
|
9
9
|
sendNotification,
|
|
10
10
|
moveTo
|
|
11
11
|
} = require('../lib/util');
|
|
12
|
-
const { handle_inbox
|
|
12
|
+
const { handle_inbox } = require('../lib/handler');
|
|
13
13
|
const logger = getLogger();
|
|
14
14
|
|
|
15
15
|
let INBOX_URL = 'inbox/';
|
|
16
16
|
let INBOX_PATH = './inbox';
|
|
17
|
+
let INBOX_BASE_URL = 'http://localhost:8000';
|
|
18
|
+
let INBOX_PUBLIC_READABLE = 0;
|
|
17
19
|
let JSON_SCHEMA = '';
|
|
18
20
|
|
|
19
21
|
function inbox_server(options) {
|
|
20
22
|
INBOX_URL = options['url'];
|
|
21
23
|
INBOX_PATH = options['inbox'];
|
|
24
|
+
INBOX_BASE_URL = options['base'];
|
|
25
|
+
INBOX_PUBLIC_READABLE = options['inboxPublic'];
|
|
22
26
|
JSON_SCHEMA = JSON.parse(fs.readFileSync(options['schema'], { encoding: 'utf-8'}));
|
|
23
27
|
let registry = [{ path : `${INBOX_URL}.*` , do: doInbox }];
|
|
24
28
|
|
|
@@ -44,12 +48,113 @@ function inbox_server(options) {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
function doInbox(req,res) {
|
|
47
|
-
if (req.method
|
|
51
|
+
if (req.method === 'GET' && INBOX_PUBLIC_READABLE) {
|
|
52
|
+
doInboxGET(req,res);
|
|
53
|
+
}
|
|
54
|
+
if (req.method == 'HEAD' && INBOX_PUBLIC_READABLE) {
|
|
55
|
+
doInboxHEAD(req,res);
|
|
56
|
+
}
|
|
57
|
+
else if (req.method === 'POST') {
|
|
58
|
+
doInboxPOST(req,res);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
48
61
|
logger.error(`tried method ${req.method} on inbox : forbidden`);
|
|
49
62
|
res.writeHead(403);
|
|
50
63
|
res.end('Forbidden');
|
|
51
64
|
return;
|
|
52
65
|
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function doInboxGET(req,res) {
|
|
69
|
+
const pathItem = req.url.substring(INBOX_URL.length);
|
|
70
|
+
|
|
71
|
+
logger.debug(`doInboxGET (for ${pathItem})`);
|
|
72
|
+
|
|
73
|
+
if (pathItem === '/') {
|
|
74
|
+
doInboxGET_Index(req,res);
|
|
75
|
+
}
|
|
76
|
+
else if (pathItem.match(/^\/[a-z0-9]+\.jsonld$/)) {
|
|
77
|
+
doInboxGET_Read(req,res);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
res.writeHead(403);
|
|
81
|
+
res.end('Forbidden');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function doInboxHEAD(req,res) {
|
|
86
|
+
const pathItem = req.url.substring(INBOX_URL.length);
|
|
87
|
+
|
|
88
|
+
logger.debug(`doInboxHEAD (for ${pathItem})`);
|
|
89
|
+
|
|
90
|
+
if (pathItem === '/') {
|
|
91
|
+
res.setHeader('Content-Type','application/ld+json');
|
|
92
|
+
res.writeHead(200);
|
|
93
|
+
res.end();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (pathItem.match(/^\/[a-z0-9]+\.jsonld$/)) {
|
|
98
|
+
const id = pathItem.substring(1);
|
|
99
|
+
const result = getBody(id);
|
|
100
|
+
|
|
101
|
+
if (result) {
|
|
102
|
+
res.setHeader('Content-Type','application/ld+json');
|
|
103
|
+
res.writeHead(200);
|
|
104
|
+
res.end();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
res.writeHead(403);
|
|
109
|
+
res.end('Forbidden');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
res.writeHead(403);
|
|
114
|
+
res.end('Forbidden');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function doInboxGET_Index(req,res) {
|
|
120
|
+
const notifications = listInbox().map( (e) => {
|
|
121
|
+
return INBOX_BASE_URL + '/' + INBOX_URL + e;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = {
|
|
125
|
+
"@context": "http://www.w3.org/ns/ldp",
|
|
126
|
+
"@id": INBOX_BASE_URL + '/' + INBOX_URL,
|
|
127
|
+
"contains": notifications
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
res.writeHead(200);
|
|
131
|
+
res.end(JSON.stringify(result,null,2));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function doInboxGET_Read(req,res) {
|
|
135
|
+
const pathItem = req.url.substring(INBOX_URL.length);
|
|
136
|
+
|
|
137
|
+
const result = getBody(pathItem.substring(1));
|
|
138
|
+
|
|
139
|
+
if (result) {
|
|
140
|
+
res.setHeader('Content-Type','application/ld+json');
|
|
141
|
+
res.writeHead(200);
|
|
142
|
+
res.end(result);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
res.writeHead(403);
|
|
146
|
+
res.end('Forbidden');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function doInboxPOST(req,res) {
|
|
151
|
+
const pathItem = req.url.substring(INBOX_URL.length);
|
|
152
|
+
|
|
153
|
+
if (pathItem !== '/') {
|
|
154
|
+
req.writeHead(403);
|
|
155
|
+
res.end('Forbidden');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
53
158
|
|
|
54
159
|
const headers = req.headers;
|
|
55
160
|
|
|
@@ -88,6 +193,35 @@ function doInbox(req,res) {
|
|
|
88
193
|
});
|
|
89
194
|
}
|
|
90
195
|
|
|
196
|
+
function listInbox() {
|
|
197
|
+
const glob = new RegExp("^.*\\.jsonld$");
|
|
198
|
+
|
|
199
|
+
logger.debug(`listInbox()`);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const entries = fs.readdirSync(INBOX_PATH).filter( (file) => {
|
|
203
|
+
return file.match(glob);
|
|
204
|
+
});
|
|
205
|
+
return entries;
|
|
206
|
+
}
|
|
207
|
+
catch(e) {
|
|
208
|
+
logger.error(e);
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getBody(id) {
|
|
214
|
+
logger.debug(`getBody(${id})`);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
return fs.readFileSync(INBOX_PATH + '/' + id, {encoding : 'utf-8'});
|
|
218
|
+
}
|
|
219
|
+
catch(e) {
|
|
220
|
+
logger.error(e);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
91
225
|
function storeBody(data) {
|
|
92
226
|
try {
|
|
93
227
|
const id = md5(data);
|
|
@@ -139,6 +273,5 @@ module.exports = {
|
|
|
139
273
|
fetchOriginal ,
|
|
140
274
|
moveTo ,
|
|
141
275
|
sendNotification ,
|
|
142
|
-
defaultSendNotificationHandler ,
|
|
143
276
|
handle_inbox
|
|
144
277
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const logger = require('../lib/util.js').getLogger();
|
|
3
|
+
const { sendNotification } = require('../lib/util.js');
|
|
4
|
+
|
|
5
|
+
async function handle({path,options}) {
|
|
6
|
+
try {
|
|
7
|
+
const json = fs.readFileSync(path, { encoding: 'utf-8'});
|
|
8
|
+
const data = JSON.parse(json);
|
|
9
|
+
const type = data['type'];
|
|
10
|
+
let inbox;
|
|
11
|
+
|
|
12
|
+
if (data['target'] && data['target']['inbox']) {
|
|
13
|
+
inbox = data['target']['inbox'];
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
throw new Error(`no target.inbox defined for ${path}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logger.info(`Sending ${type} to ${inbox}`);
|
|
20
|
+
await sendNotification(inbox, data);
|
|
21
|
+
|
|
22
|
+
return { path, options, success: true };
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
logger.error(e);
|
|
26
|
+
return { path, options, success: false };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { handle };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldn-inbox-server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "A demonstration Event Notifications Inbox server",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"author": "Patrick Hochstenbach <Patrick.Hochstenbach@UGent.be>",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"demo-post": "curl -X POST -H 'Content-Type: application/ld+json' --data-binary '@examples/offer.jsonld' http://localhost:8000/inbox/",
|
|
11
11
|
"handle-inbox": "npx ldn-inbox-server handler @inbox -hn ./handler/demo_notification_handler.js",
|
|
12
12
|
"handle-outbox": "npx ldn-inbox-server handler @outbox",
|
|
13
|
+
"handle-eventlog": "npx ldn-inbox-server handler @inbox -hn handler/eventlog_notification_handler.js",
|
|
13
14
|
"clean": "rm error/* inbox/* outbox/*"
|
|
14
15
|
},
|
|
15
16
|
"bin": "./bin/ldn-inbox-server.js",
|
|
File without changes
|
|
File without changes
|