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 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=0
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
 
@@ -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 ?? './config/notification_schema.json';
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;
@@ -27,6 +27,7 @@
27
27
  },
28
28
  {
29
29
  "id": "@handler/notification_handler/offer_memento.js",
30
+ "$sequential": true,
30
31
  "actor": {
31
32
  "id": "http://localhost:8000/profile/card#me" ,
32
33
  "inbox": "http://localhost:8000/inbox/" ,
@@ -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 config = parseConfig(options['config']);
22
+ const mainConfig = parseConfig(options['config']);
16
23
 
17
- if (! config) {
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 notification = parseAsJSON(path);
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 handler({path,options,config,notification});
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.js').getLogger();
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({path: fullPath, options: options}, { name: 'handle'})
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' && opts['inboxPublic'] == 1) {
20
+ if (req.method === 'GET') {
21
21
  doInboxGET(req,res,opts);
22
22
  }
23
- else if (req.method == 'HEAD' && opts['inboxPublic'] == 1) {
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
- const notifications = listInbox(opts).map( (e) => {
110
- return opts['base'] + '/' + opts['url'] + e;
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
- logger.debug(`accepted ${req.url}${id}`);
196
- res.setHeader('Location',`${req.url}${id}`);
197
- res.writeHead(201);
198
- res.end(`Accepted ${req.url}${id}`);
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 (! (opts['schema'] && fs.existsSync(opts['schema']))) {
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.7.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": [