ldn-inbox-server 1.2.2 → 1.3.1
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 +4 -1
- package/README.md +61 -6
- package/bin/ldn-inbox-server.js +12 -5
- package/bin/post-notification.js +24 -0
- package/config/inbox_config.json +31 -0
- package/config/outbox_config.json +15 -0
- package/examples/offer.jsonld +2 -2
- package/handler/{demo_notification_handler.js → accept_notification_handler.js} +19 -9
- package/handler/demo_inbox_handler.js +6 -2
- package/handler/eventlog_notification_handler.js +104 -21
- package/handler/multi_notification_handler.js +61 -0
- package/handler/offer_memento_notification_handler.js +68 -0
- package/{lib/sendNotificationHandler.js → handler/send_notification_handler.js} +6 -3
- package/handler/type_filter_notification_handler.js +65 -0
- package/handler/valid_artifact_notification_handler.js +55 -0
- package/lib/handler.js +1 -0
- package/lib/index.js +3 -3
- package/lib/util.js +69 -2
- package/package.json +8 -5
- package/public/artifacts-example/artifact1.html +17 -0
- package/public/artifacts-example/artifact1.html.meta +6 -0
- package/public/artifacts-example/artifact2.html +17 -0
- package/public/artifacts-example/artifact2.html.meta +6 -0
- package/public/index.html.meta +3 -0
package/.env-example
CHANGED
|
@@ -9,4 +9,7 @@ LDN_SERVER_INBOX_PATH=./inbox
|
|
|
9
9
|
LDN_SERVER_ERROR_PATH=./error
|
|
10
10
|
LDN_SERVER_OUTBOX_PATH=./outbox
|
|
11
11
|
LDN_SERVER_PUBLIC_PATH=./public
|
|
12
|
-
LDN_SERVER_JSON_SCHEMA=./config/notification_schema.json
|
|
12
|
+
LDN_SERVER_JSON_SCHEMA=./config/notification_schema.json
|
|
13
|
+
LDN_SERVER_INBOX_CONFIG=./config/inbox_config.json
|
|
14
|
+
LDN_SERVER_OUTBOX_CONFIG=./config/outbox_config.json
|
|
15
|
+
LDN_SERVER_HAS_PUBLIC_INBOX=0
|
package/README.md
CHANGED
|
@@ -33,13 +33,13 @@ curl -X POST -H 'Content-Type: application/ld+json' --data-binary '@examples/off
|
|
|
33
33
|
Start an inbox handler with a demo handler (that creates an `Accept` message in the `./outbox`).
|
|
34
34
|
|
|
35
35
|
```
|
|
36
|
-
npx ldn-inbox-server
|
|
36
|
+
npx ldn-inbox-server handler @inbox -hn ./handler/accept_notification_handler.js
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Start an outbox handler that send the notifications that are available outbox:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
npx ldn-inbox-server
|
|
42
|
+
npx ldn-inbox-server handler @outbox -hn ./handler/send_notification_handler.js
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
## Environment
|
|
@@ -62,7 +62,7 @@ npx ldn-inbox-server handle @outbox
|
|
|
62
62
|
Server extensions are possible by providing custom inbox and notification handlers. E.g.
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
npx ldn-inbox-server
|
|
65
|
+
npx ldn-inbox-server handler @inbox -hn handler/my_handler.js
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
Or, in JavaScript:
|
|
@@ -74,12 +74,19 @@ main();
|
|
|
74
74
|
|
|
75
75
|
async function main() {
|
|
76
76
|
await handle_inbox('./inbox', {
|
|
77
|
-
'
|
|
77
|
+
'inbox': './inbox',
|
|
78
|
+
'outbox': './outbox',
|
|
79
|
+
'public': './public',
|
|
80
|
+
'error': './error',
|
|
81
|
+
'batch_size': 5,
|
|
82
|
+
'glob': '^.*\\.jsonld$',
|
|
83
|
+
'config': './config/inbox_config.json',
|
|
84
|
+
'notification_handler': 'handler/my_handler.js'
|
|
78
85
|
});
|
|
79
86
|
}
|
|
80
87
|
```
|
|
81
88
|
|
|
82
|
-
with `
|
|
89
|
+
with `my_handler.js` :
|
|
83
90
|
|
|
84
91
|
```
|
|
85
92
|
async function handle({path,options}) {
|
|
@@ -102,6 +109,54 @@ A handler can be started on any directory. E.g. a workflow might be:
|
|
|
102
109
|
- processed LDN messages will end up in the "outbox" box
|
|
103
110
|
- invalid processing will be saved into the "error" box
|
|
104
111
|
|
|
112
|
+
## Handlers
|
|
113
|
+
|
|
114
|
+
### Accept handler
|
|
115
|
+
|
|
116
|
+
A handler that creates for an incoming notification an `Accept` notification in the `@outbox`.
|
|
117
|
+
|
|
118
|
+
### Evenlog handler
|
|
119
|
+
|
|
120
|
+
A handler that updates an event log with the incoming notification.
|
|
121
|
+
|
|
122
|
+
Requires a configuration file with property `notification_handler.eventlog`:
|
|
123
|
+
|
|
124
|
+
- `log`: the path to the event log (starting from the `public` directory)
|
|
125
|
+
- `dir`: the path to a container to store the events (starting from the `public` directory)
|
|
126
|
+
|
|
127
|
+
The `log` and `dir` path may contain a `@artifact(:strip)?@` directive to fill in the
|
|
128
|
+
path of the current artifact. The `:strip` filter is used to strip an artifact path of a file extension. E.g. `path/artifact.html` becomes `path/artifact` when using a `:strip`.
|
|
129
|
+
|
|
130
|
+
### Multi handler
|
|
131
|
+
|
|
132
|
+
A `handler/multi_notification_handler.js` is available to start multiple handler for each notification messages. The handlers to start are specified in a configuraton file that can be passed via the `config` parameter of an `handle_inbox`. In the commmand line tool `bin/ldn-inbox-server` the default location of such config file is `config/inbox_config.json` when processing an `@inbox`, and `config/outbox_config.json` when processing an `@outbox`.
|
|
133
|
+
|
|
134
|
+
### Offer memento handler
|
|
135
|
+
|
|
136
|
+
A handler to `Offer` an event log to a memento server.
|
|
137
|
+
|
|
138
|
+
Requires a configuration file with property `notification_handler.offer_memento`:
|
|
139
|
+
|
|
140
|
+
- `actor`: the LDN+AS2 `actor` to use in a notification
|
|
141
|
+
- `target`: the LDN+AS2 `target` to use in a notification
|
|
142
|
+
|
|
143
|
+
### Send handler
|
|
144
|
+
|
|
145
|
+
A hanlder to send notification that live in the `@outbox` via the LDN protocol to the LDN `target`.
|
|
146
|
+
|
|
147
|
+
### Valid artifact
|
|
148
|
+
|
|
149
|
+
A handler that validates the incoming notification and checks if the `object` or `context` contains an artifact that is part of the `public` resources. See 'Artifact support' below.
|
|
150
|
+
|
|
151
|
+
## Artifact support
|
|
152
|
+
|
|
153
|
+
This code base contains Event Notifications support for Data Node artifacts. See the examples
|
|
154
|
+
in `public/artifacts-example`. Move this directory tp `public/artifacts` to get a running example.
|
|
155
|
+
|
|
156
|
+
- Each artifact requires at least a `.meta` file with the `X-Artifact` header set to `true` to be recognized by the software as an artifact
|
|
157
|
+
- Each artifact should update the `Link-Template` header in the `.meta` file
|
|
158
|
+
- The config files in `config/inbox_config.json` and `config/outbox_config.json` define the location of the possible event logs for the artifact
|
|
159
|
+
|
|
105
160
|
## See also
|
|
106
161
|
|
|
107
162
|
- [mellon-server](https://www.npmjs.com/package/mellon-server)
|
package/bin/ldn-inbox-server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require('fs');
|
|
4
4
|
const { program } = require('commander');
|
|
5
5
|
const { inbox_server } = require('../lib/index');
|
|
6
|
-
const { handle_inbox
|
|
6
|
+
const { handle_inbox } = require('../lib/handler');
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
|
|
9
9
|
const HOST = process.env.LDN_SERVER_HOST ?? 'localhost';
|
|
@@ -17,11 +17,12 @@ const INBOX_PATH = process.env.LDN_SERVER_INBOX_PATH ?? './inbox';
|
|
|
17
17
|
const ERROR_PATH = process.env.LDN_SERVER_ERROR_PATH ?? './error';
|
|
18
18
|
const OUTBOX_PATH = process.env.LDN_SERVER_OUTBOX_PATH ?? './outbox';
|
|
19
19
|
const JSON_SCHEMA_PATH = process.env.LDN_SERVER_JSON_SCHEMA ?? './config/notification_schema.json';
|
|
20
|
+
const INBOX_CONFIG = process.env.LDN_SERVER_INBOX_CONFIG;
|
|
21
|
+
const OUTBOX_CONFIG = process.env.LDN_SERVER_OUTBOX_CONFIG;
|
|
20
22
|
const HAS_PUBLIC = process.env.LDN_SERVER_HAS_PUBLIC_INBOX ?? 0;
|
|
21
23
|
|
|
22
24
|
program
|
|
23
25
|
.name('lnd-inbox-server')
|
|
24
|
-
.version('1.2.2')
|
|
25
26
|
.description('A demonstration Event Notifications Inbox server');
|
|
26
27
|
|
|
27
28
|
program
|
|
@@ -41,6 +42,7 @@ program
|
|
|
41
42
|
|
|
42
43
|
program
|
|
43
44
|
.command('handler')
|
|
45
|
+
.option('--base <url>','base url',INBOX_BASE_URL)
|
|
44
46
|
.option('--inbox <inbox>','inbox',INBOX_PATH)
|
|
45
47
|
.option('--outbox <outbox>','outbox',OUTBOX_PATH)
|
|
46
48
|
.option('--public <public>','public',PUBLIC_PATH)
|
|
@@ -48,6 +50,7 @@ program
|
|
|
48
50
|
.option('--loop <seconds>', 'run in a loop',0)
|
|
49
51
|
.option('--batch_size <num>','batch size to process',INBOX_BATCH_SIZE)
|
|
50
52
|
.option('--glob <glob>','files to process in inbox',INBOX_GLOB)
|
|
53
|
+
.option('--config <path>','config file for handlers')
|
|
51
54
|
.option('-hi,--inbox_handler <handler>','inbox handler')
|
|
52
55
|
.option('-hn,--notification_handler <handler>','notification handler')
|
|
53
56
|
.argument('<box>','box to process')
|
|
@@ -55,11 +58,15 @@ program
|
|
|
55
58
|
switch (box) {
|
|
56
59
|
case '@inbox':
|
|
57
60
|
box = INBOX_PATH;
|
|
61
|
+
if (!options['config'] && fs.existsSync(INBOX_CONFIG)) {
|
|
62
|
+
options['config'] = INBOX_CONFIG
|
|
63
|
+
}
|
|
58
64
|
break;
|
|
59
65
|
case '@outbox':
|
|
60
66
|
box = OUTBOX_PATH;
|
|
61
|
-
options['
|
|
62
|
-
|
|
67
|
+
if (!options['config'] && fs.existsSync(OUTBOX_CONFIG)) {
|
|
68
|
+
options['config'] = OUTBOX_CONFIG
|
|
69
|
+
}
|
|
63
70
|
break;
|
|
64
71
|
}
|
|
65
72
|
if (options['loop']) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { program } = require('commander');
|
|
5
|
+
const { sendNotification, getLogger } = require('../lib/util.js');
|
|
6
|
+
require('dotenv').config();
|
|
7
|
+
|
|
8
|
+
const logger = getLogger();
|
|
9
|
+
|
|
10
|
+
const INBOX_URL = process.env.LDN_SERVER_INBOX_URL ?? 'inbox/';
|
|
11
|
+
const INBOX_BASE_URL = process.env.LDN_SERVER_BASEURL ?? 'http://localhost:8000';
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('post-notification')
|
|
15
|
+
.description('A LDN notification sender')
|
|
16
|
+
.argument('<url>','notification')
|
|
17
|
+
.argument('<file>','notification')
|
|
18
|
+
.action( async(url,file) => {
|
|
19
|
+
const to = url === '@me' ? `${INBOX_BASE_URL}/${INBOX_URL}` : url;
|
|
20
|
+
const json = JSON.parse(fs.readFileSync(file, { encoding: 'utf-8'}));
|
|
21
|
+
await sendNotification(to,json);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
program.parse();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"notification_handler": {
|
|
3
|
+
"eventlog": {
|
|
4
|
+
"log": "@artifact:strip@.jsonld",
|
|
5
|
+
"dir": "@artifact:strip@"
|
|
6
|
+
},
|
|
7
|
+
"multi": {
|
|
8
|
+
"handlers": [
|
|
9
|
+
"handler/type_filter_notification_handler.js",
|
|
10
|
+
"handler/valid_artifact_notification_handler.js" ,
|
|
11
|
+
"handler/eventlog_notification_handler.js",
|
|
12
|
+
"handler/offer_memento_notification_handler.js"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"offer_memento": {
|
|
16
|
+
"actor": {
|
|
17
|
+
"id": "http://localhost:8000/profile/card#me" ,
|
|
18
|
+
"inbox": "http://localhost:8000/inbox/" ,
|
|
19
|
+
"type": "Service"
|
|
20
|
+
},
|
|
21
|
+
"target": {
|
|
22
|
+
"id": "http://localhost:8001/profile/card#me" ,
|
|
23
|
+
"inbox": "http://localhost:8001/inbox/" ,
|
|
24
|
+
"type": "Service"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"type_filter": {
|
|
28
|
+
"anyOf": [ "Offer" ]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"notification_handler": {
|
|
3
|
+
"eventlog": {
|
|
4
|
+
"log": "@artifact:strip@.jsonld",
|
|
5
|
+
"dir": "@artifact:strip@"
|
|
6
|
+
},
|
|
7
|
+
"multi": {
|
|
8
|
+
"handlers": [
|
|
9
|
+
"handler/valid_artifact_notification_handler.js" ,
|
|
10
|
+
"handler/send_notification_handler.js",
|
|
11
|
+
"handler/eventlog_notification_handler.js"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/examples/offer.jsonld
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
"type": "Offer",
|
|
8
8
|
"actor": {
|
|
9
9
|
"id": "https://acme.org.xyz/profile/card#us",
|
|
10
|
-
"inbox": "
|
|
10
|
+
"inbox": "http://localhost:8000/inbox/",
|
|
11
11
|
"type": "Organization"
|
|
12
12
|
},
|
|
13
13
|
"object": {
|
|
14
|
-
"id": "http://
|
|
14
|
+
"id": "http://localhost:8000/artifacts/artifact1.html",
|
|
15
15
|
"type": [ "Document" , "schema:Dataset" ]
|
|
16
16
|
},
|
|
17
17
|
"target": {
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const md5 = require('md5');
|
|
3
|
+
const { parseAsJSON } = require('../lib/util');
|
|
2
4
|
const logger = require('../lib/util.js').getLogger();
|
|
3
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Demonstration notification handler, that creates an 'Accept'
|
|
8
|
+
* notification for each incoming notification message and stores
|
|
9
|
+
* it in the outbox container.
|
|
10
|
+
*/
|
|
4
11
|
async function handle({path,options}) {
|
|
5
12
|
logger.info(`parsing notification ${path}`);
|
|
6
13
|
|
|
7
14
|
try {
|
|
8
|
-
const json =
|
|
15
|
+
const json = parseAsJSON(path);
|
|
9
16
|
|
|
10
|
-
const outboxFile = options['outbox'] + '/' + path.split('/').pop();
|
|
11
|
-
|
|
12
17
|
const id = json['id'];
|
|
13
18
|
const object = json['object'];
|
|
14
19
|
const actor_id = json['actor']['id'];
|
|
15
20
|
const actor_type = json['actor']['type'];
|
|
16
21
|
const actor_inbox = json['actor']['inbox'];
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
fs.writeFileSync(outboxFile, JSON.stringify({
|
|
23
|
+
const data = JSON.stringify({
|
|
21
24
|
type: 'Accept',
|
|
22
25
|
actor: {
|
|
23
26
|
id: 'http://my.server' ,
|
|
@@ -32,13 +35,20 @@ async function handle({path,options}) {
|
|
|
32
35
|
type: actor_type ,
|
|
33
36
|
inbox: actor_inbox
|
|
34
37
|
}
|
|
35
|
-
},null,
|
|
36
|
-
|
|
38
|
+
},null,4);
|
|
39
|
+
|
|
40
|
+
const outboxFile = options['outbox'] + '/' + md5(data) + '.jsonld';
|
|
41
|
+
|
|
42
|
+
logger.info(`storing Accept to ${outboxFile}`);
|
|
43
|
+
|
|
44
|
+
fs.writeFileSync(outboxFile,data);
|
|
45
|
+
|
|
46
|
+
return { path, options, success: true };
|
|
37
47
|
}
|
|
38
48
|
catch(e) {
|
|
39
49
|
logger.error(`failed to process ${path}`);
|
|
40
50
|
logger.debug(e);
|
|
41
|
-
return { path,options, success: false };
|
|
51
|
+
return { path, options, success: false };
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Demonstration of an alternatve inbox handler
|
|
5
|
+
* (don't use it the one in lib/handler.js is much more mature)
|
|
6
|
+
* To use it set it via the `--hi` option of `bin/ldn-inbox-server.js`, or
|
|
7
|
+
* the `inbox_handler` option of the `handle_inbox` function.
|
|
8
|
+
*/
|
|
5
9
|
async function handle({path,options}) {
|
|
6
10
|
console.log(`handleInbox(${path},..)`);
|
|
7
11
|
fs.readdir(path, (err,files) => {
|
|
@@ -1,45 +1,121 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const md5 = require('md5');
|
|
2
3
|
const fsPath = require('path');
|
|
3
4
|
const lockfile = require('proper-lockfile');
|
|
5
|
+
const { parseAsJSON } = require('../lib/util');
|
|
4
6
|
const logger = require('../lib/util.js').getLogger();
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Demonstration event log handler
|
|
10
|
+
*/
|
|
9
11
|
async function handle({path,options}) {
|
|
10
12
|
logger.info(`parsing notification ${path}`);
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
const config = parseAsJSON(options['config']);
|
|
15
|
+
|
|
16
|
+
if (! config) {
|
|
17
|
+
logger.error('no configuration found for eventlog_notification_handler');
|
|
18
|
+
return { path, options, success: false };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const thisConfig = config['notification_handler']?.['eventlog'];
|
|
22
|
+
|
|
23
|
+
if (! thisConfig || !thisConfig['log'] || !thisConfig['dir']) {
|
|
24
|
+
logger.error('no log/dir entry for notification_handler.eventlog configuration');
|
|
25
|
+
return { path, options, success: false };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const base = options['base'] ? options['base'] :
|
|
29
|
+
options['host'] && options['port'] ?
|
|
30
|
+
`http://${options['host']}:${options['port']}` :
|
|
31
|
+
'http://localhost:8000';
|
|
32
|
+
|
|
33
|
+
let eventLog = thisConfig['log'];
|
|
34
|
+
let eventDir = thisConfig['dir'];
|
|
35
|
+
|
|
36
|
+
let artifactPath = undefined;
|
|
37
|
+
|
|
38
|
+
if (options['artifact']) {
|
|
39
|
+
artifactPath = options['artifact']['id'].substring(base.length+1);
|
|
40
|
+
logger.info(artifactPath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (eventLog.match(/@artifact(:[a-zA-Z]+)?@/)) {
|
|
44
|
+
if (options['artifact']) {
|
|
45
|
+
if (eventLog.match(/@artifact:strip@/)) {
|
|
46
|
+
eventLog = eventLog.replaceAll(/@artifact(:[a-zA-Z]+)?@/g,artifactPath.replaceAll(/\.\w+$/g,''));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
eventLog = eventLog.replaceAll(/@artifact(:[a-zA-Z]+)?@/g,artifactPath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
logger.error(`requested to parse ${eventLog} but no artifact information available`);
|
|
54
|
+
return { path, options, success: false };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (eventDir.match(/@artifact(:[a-zA-Z]+)?@/)) {
|
|
59
|
+
if (options['artifact']) {
|
|
60
|
+
if (eventDir.match(/@artifact:strip@/)) {
|
|
61
|
+
eventDir = eventDir.replaceAll(/@artifact(:[a-zA-Z]+)?@/g,artifactPath.replaceAll(/\.\w+$/g,''));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
eventDir = eventDir.replaceAll(/@artifact(:[a-zA-Z]+)?@/g,artifactPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
logger.error(`requested to parse ${eventDir} but no artifact information available`);
|
|
69
|
+
return { path, options, success: false };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
12
73
|
try {
|
|
13
|
-
const json =
|
|
74
|
+
const json = fs.readFileSync(path, { encoding: 'utf-8' });
|
|
14
75
|
|
|
15
76
|
const fileName = path.split('/').pop();
|
|
16
|
-
const logDir = fsPath.join(options['public'],EVENT_DIR,'log');
|
|
17
77
|
|
|
18
|
-
if (! fs.existsSync(
|
|
19
|
-
logger.info(`creating ${
|
|
20
|
-
fs.mkdirSync(
|
|
78
|
+
if (! fs.existsSync(`${options['public']}/${eventDir}`)) {
|
|
79
|
+
logger.info(`creating ${options['public']}/${eventDir}`);
|
|
80
|
+
fs.mkdirSync(`${options['public']}/${eventDir}`, { recursive : true });
|
|
21
81
|
}
|
|
22
|
-
|
|
23
|
-
const outboxFile = fsPath.join(logDir,fileName);
|
|
24
82
|
|
|
25
|
-
|
|
83
|
+
const eventFile = `${options['public']}/${eventDir}/${fileName}`;
|
|
84
|
+
|
|
85
|
+
fs.writeFileSync(eventFile, json);
|
|
26
86
|
|
|
27
87
|
// Updating metadata file
|
|
28
|
-
const metaFile =
|
|
88
|
+
const metaFile = `${options['public']}/${eventLog}.meta`;
|
|
29
89
|
|
|
30
90
|
fs.writeFileSync(metaFile, JSON.stringify({
|
|
31
91
|
'Content-Type': 'application/ld+json',
|
|
32
92
|
'Last-Modified': nowISO()
|
|
33
93
|
},null,4));
|
|
34
94
|
|
|
95
|
+
// Store the path in the options .. yeah yeah we know ugly but it works for now
|
|
96
|
+
|
|
97
|
+
const eventPath = `${eventDir}/${fileName}`;
|
|
98
|
+
const eventId = `${base}/${eventPath}`;
|
|
99
|
+
const eventLogId = `${base}/${eventLog}`;
|
|
100
|
+
|
|
101
|
+
options['eventlog'] = {
|
|
102
|
+
'id': eventLogId ,
|
|
103
|
+
'file': `${options['public']}/${eventLog}` ,
|
|
104
|
+
'dir': `${options['public']}/${eventDir}` ,
|
|
105
|
+
'item': {
|
|
106
|
+
'id' : eventId ,
|
|
107
|
+
'file' : eventFile
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
35
111
|
await updateEventLog({path,options});
|
|
36
112
|
|
|
37
|
-
return { path,options, success: true };
|
|
113
|
+
return { path, options, success: true };
|
|
38
114
|
}
|
|
39
115
|
catch(e) {
|
|
40
116
|
logger.error(`failed to process ${path}`);
|
|
41
117
|
logger.error(e);
|
|
42
|
-
return { path,options, success: false };
|
|
118
|
+
return { path, options, success: false };
|
|
43
119
|
}
|
|
44
120
|
}
|
|
45
121
|
|
|
@@ -47,11 +123,11 @@ async function updateEventLog({path,options}) {
|
|
|
47
123
|
logger.info(`updating eventlog for ${path}`);
|
|
48
124
|
|
|
49
125
|
try {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const entry = `${baseUrl}/${EVENT_DIR}/log/${fileName}`;
|
|
126
|
+
const notification = fs.readFileSync(path, { encoding: 'utf-8'});
|
|
127
|
+
const notification_checksum = md5(notification);
|
|
53
128
|
|
|
54
|
-
const
|
|
129
|
+
const entry = options['eventlog']['item']['id'];
|
|
130
|
+
const eventLog = options['eventlog']['file'];
|
|
55
131
|
|
|
56
132
|
let json;
|
|
57
133
|
|
|
@@ -66,13 +142,20 @@ async function updateEventLog({path,options}) {
|
|
|
66
142
|
};
|
|
67
143
|
}
|
|
68
144
|
|
|
69
|
-
if (json['member'].findIndex( (e) => e === entry) >= 0) {
|
|
145
|
+
if (json['member'].findIndex( (e) => e['id'] === entry) >= 0) {
|
|
70
146
|
logger.info(`${entry} already in ${eventLog}`);
|
|
71
147
|
}
|
|
72
148
|
else {
|
|
73
149
|
logger.info(`updating ${eventLog}`);
|
|
74
150
|
|
|
75
|
-
json['member'].push(
|
|
151
|
+
json['member'].push({
|
|
152
|
+
"id": entry ,
|
|
153
|
+
"checksum": {
|
|
154
|
+
"type": "Checksum",
|
|
155
|
+
"algorithm": "spdx:checksumAlgorithm_md5",
|
|
156
|
+
"checksumValue": notification_checksum
|
|
157
|
+
}
|
|
158
|
+
});
|
|
76
159
|
|
|
77
160
|
if (fs.existsSync(eventLog)) {
|
|
78
161
|
try {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const { dynamic_handler , parseAsJSON } = require('../lib/util');
|
|
2
|
+
const logger = require('../lib/util.js').getLogger();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Demonstration notification handler that start multiple notification handlers.
|
|
6
|
+
* Requires a config file that specifies which handlers to start.
|
|
7
|
+
*/
|
|
8
|
+
async function handle({path,options}) {
|
|
9
|
+
let success = false;
|
|
10
|
+
|
|
11
|
+
const config = parseAsJSON(options['config']);
|
|
12
|
+
|
|
13
|
+
if (! config) {
|
|
14
|
+
logger.error('no configuration found for multi_notification_handler');
|
|
15
|
+
return { path, options, success: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const handlers = config['notification_handler']?.['multi']?.['handlers'];
|
|
19
|
+
|
|
20
|
+
if (! handlers) {
|
|
21
|
+
logger.error('no notification_handler.multi.handlers key in configuration file');
|
|
22
|
+
return { path, options, success: false };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
logger.info(`starting multi handler`);
|
|
27
|
+
|
|
28
|
+
for (let i = 0 ; i < handlers.length ; i++) {
|
|
29
|
+
logger.info(`starting ${handlers[i]}`);
|
|
30
|
+
|
|
31
|
+
const handler = dynamic_handler(handlers[i],null);
|
|
32
|
+
|
|
33
|
+
if (! handler) {
|
|
34
|
+
throw new Error(`failed to load ${handlers[i]}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = await handler({path,options});
|
|
38
|
+
|
|
39
|
+
if (result['success']) {
|
|
40
|
+
logger.info(`finished ${handlers[i]}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error(`Eek! ${handlers[i]} failed`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
success = true;
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
logger.error(`failed to process ${path}`);
|
|
51
|
+
logger.error(e);
|
|
52
|
+
|
|
53
|
+
success = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
logger.info(`finished multi handler`);
|
|
57
|
+
|
|
58
|
+
return { path, options, success: success };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { handle };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const md5 = require('md5');
|
|
3
|
+
const { parseAsJSON } = require('../lib/util.js');
|
|
4
|
+
const logger = require('../lib/util.js').getLogger();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Demonstration notification handler, that creates an 'Offer'
|
|
8
|
+
* notification for each incoming notificaation to request the
|
|
9
|
+
* archivation of the event log.
|
|
10
|
+
*/
|
|
11
|
+
async function handle({path,options}) {
|
|
12
|
+
logger.info(`parsing notification ${path}`);
|
|
13
|
+
|
|
14
|
+
const config = parseAsJSON(options['config']);
|
|
15
|
+
|
|
16
|
+
if (! config) {
|
|
17
|
+
logger.error('no configuration found for offer_memento_notification_handler');
|
|
18
|
+
return { path, options, success: false };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const thisConfig = config['notification_handler']?.['offer_memento'];
|
|
22
|
+
|
|
23
|
+
if (! thisConfig || !thisConfig['actor'] || !thisConfig['target']) {
|
|
24
|
+
logger.error('no actor/target entry for notification_handler.eventlog configuration');
|
|
25
|
+
return { path, options, success: false };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!options['artifact']) {
|
|
29
|
+
logger.info(`no artifact found (ignonring this request)`);
|
|
30
|
+
return { path, options, success: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!options['eventlog']) {
|
|
34
|
+
logger.info(`no artifact found (ignonring this request)`);
|
|
35
|
+
return { path, options, success: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.stringify({
|
|
40
|
+
'@context': [
|
|
41
|
+
"https://www.w3.org/ns/activitystreams" ,
|
|
42
|
+
{"schema": "https://schema.org/"}
|
|
43
|
+
],
|
|
44
|
+
type: 'Offer',
|
|
45
|
+
actor: thisConfig['actor'],
|
|
46
|
+
object: {
|
|
47
|
+
id: options['eventlog']['id'],
|
|
48
|
+
type: [ "Document", "schema:Dataset" ]
|
|
49
|
+
},
|
|
50
|
+
target: thisConfig['target']
|
|
51
|
+
},null,4);
|
|
52
|
+
|
|
53
|
+
const outboxFile = options['outbox'] + '/' + md5(data) + '.jsonld';
|
|
54
|
+
|
|
55
|
+
logger.info(`storing Offer to ${outboxFile}`);
|
|
56
|
+
|
|
57
|
+
fs.writeFileSync(outboxFile,data);
|
|
58
|
+
|
|
59
|
+
return { path, options, success: true };
|
|
60
|
+
}
|
|
61
|
+
catch(e) {
|
|
62
|
+
logger.error(`failed to process ${path}`);
|
|
63
|
+
logger.debug(e);
|
|
64
|
+
return { path, options, success: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { handle };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
1
|
const logger = require('../lib/util.js').getLogger();
|
|
3
|
-
const { sendNotification } = require('../lib/util.js');
|
|
2
|
+
const { sendNotification , parseAsJSON } = require('../lib/util.js');
|
|
4
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Demonstration notification handler that sends a notification to a
|
|
6
|
+
* target inbox.
|
|
7
|
+
*/
|
|
5
8
|
async function handle({path,options}) {
|
|
6
9
|
try {
|
|
7
|
-
const json =
|
|
10
|
+
const json = parseAsJSON(path);
|
|
8
11
|
const data = JSON.parse(json);
|
|
9
12
|
const type = data['type'];
|
|
10
13
|
let inbox;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { parseAsJSON } = require('../lib/util');
|
|
3
|
+
const logger = require('../lib/util.js').getLogger();
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Demonstration notification handler, that checks if the notification
|
|
7
|
+
* matches a configurable list
|
|
8
|
+
*/
|
|
9
|
+
async function handle({path,options}) {
|
|
10
|
+
logger.info(`parsing notification ${path}`);
|
|
11
|
+
|
|
12
|
+
const config = parseAsJSON(options['config']);
|
|
13
|
+
|
|
14
|
+
if (! config) {
|
|
15
|
+
logger.error('no configuration found for eventlog_notification_handler');
|
|
16
|
+
return { path, options, success: false };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const thisConfig = config['notification_handler']?.['type_filter'];
|
|
20
|
+
|
|
21
|
+
if (! thisConfig) {
|
|
22
|
+
logger.error('no notification_handler.type_filer configuration');
|
|
23
|
+
return { path, options, success: false };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (! (thisConfig['anyOf'] && Array.isArray(thisConfig['anyOf']))) {
|
|
27
|
+
logger.error('no anyOf entry in notification_handler.type_filer should be an array');
|
|
28
|
+
return { path, options, success: false };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const json = parseAsJSON(path);
|
|
33
|
+
|
|
34
|
+
const type = json['type'];
|
|
35
|
+
|
|
36
|
+
const typeArray = Array.isArray(type) ? type : [type];
|
|
37
|
+
|
|
38
|
+
let isOk = true ;
|
|
39
|
+
|
|
40
|
+
for (let i = 0 ; i < typeArray.length ; i++) {
|
|
41
|
+
if (thisConfig['anyOf'].includes(typeArray[i])) {
|
|
42
|
+
// We are ok
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
logger.error(`${typeArray[i]} does not pass type_filter check`);
|
|
46
|
+
isOk = false;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isOk) {
|
|
52
|
+
return { path, options, success: true };
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
return { path, options, success: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch(e) {
|
|
59
|
+
logger.error(`failed to process ${path}`);
|
|
60
|
+
logger.debug(e);
|
|
61
|
+
return { path, options, success: false };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { handle };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const { ldPropertyAsId , parseArtifact, parseAsJSON } = require('../lib/util.js');
|
|
2
|
+
const logger = require('../lib/util.js').getLogger();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Demonstration notification handler, that checks if the notification
|
|
6
|
+
* message contains an artifact that is known to the data node
|
|
7
|
+
*/
|
|
8
|
+
async function handle({path,options}) {
|
|
9
|
+
logger.info(`parsing notification ${path}`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const json = parseAsJSON(path);
|
|
13
|
+
|
|
14
|
+
let artifact = undefined;
|
|
15
|
+
|
|
16
|
+
if (ldPropertyAsId(json['context'])) {
|
|
17
|
+
artifact = ldPropertyAsId(json['context']);
|
|
18
|
+
}
|
|
19
|
+
else if (ldPropertyAsId(json['object'])) {
|
|
20
|
+
artifact = ldPropertyAsId(json['object']);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
logger.error(`failed to find valid context or object`);
|
|
24
|
+
return { path, options, success: false };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!artifact) {
|
|
28
|
+
logger.error(`failed to find artifact`);
|
|
29
|
+
return { path, options, success: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const artifactPath = parseArtifact(artifact,options);
|
|
33
|
+
|
|
34
|
+
if (artifactPath) {
|
|
35
|
+
// Storing the artifact path to the options.
|
|
36
|
+
// Maybe bad practice..but it is a workflow attribute like in Nifi :P
|
|
37
|
+
options['artifact'] = {
|
|
38
|
+
'id': artifact ,
|
|
39
|
+
'path': artifactPath
|
|
40
|
+
};
|
|
41
|
+
return { path, options, success: true };
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
logger.error(`artifact ${artifact} is not known here...`);
|
|
45
|
+
return { path, options, success: false };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch(e) {
|
|
49
|
+
logger.error(`failed to process ${path}`);
|
|
50
|
+
logger.debug(e);
|
|
51
|
+
return { path, options, success: false };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { handle };
|
package/lib/handler.js
CHANGED
|
@@ -17,6 +17,7 @@ async function defaultInboxHandler({path,options}) {
|
|
|
17
17
|
|
|
18
18
|
const worker = options['notification_handler'] ?? fsPath.resolve(__dirname,'..','lib','notification.js');
|
|
19
19
|
|
|
20
|
+
// Run the notifications using a node.js worker pool
|
|
20
21
|
const pool = new piscina({
|
|
21
22
|
filename: worker,
|
|
22
23
|
maxQueue: queue_size
|
package/lib/index.js
CHANGED
|
@@ -42,16 +42,17 @@ function inbox_server(options) {
|
|
|
42
42
|
start_server({
|
|
43
43
|
host: options['host'],
|
|
44
44
|
port: options['port'],
|
|
45
|
+
base: options['base'],
|
|
45
46
|
public: options['public'],
|
|
46
47
|
registry: registry
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function doInbox(req,res) {
|
|
51
|
-
if (req.method === 'GET' && INBOX_PUBLIC_READABLE) {
|
|
52
|
+
if (req.method === 'GET' && INBOX_PUBLIC_READABLE == 1) {
|
|
52
53
|
doInboxGET(req,res);
|
|
53
54
|
}
|
|
54
|
-
if (req.method == 'HEAD' && INBOX_PUBLIC_READABLE) {
|
|
55
|
+
else if (req.method == 'HEAD' && INBOX_PUBLIC_READABLE == 1) {
|
|
55
56
|
doInboxHEAD(req,res);
|
|
56
57
|
}
|
|
57
58
|
else if (req.method === 'POST') {
|
|
@@ -61,7 +62,6 @@ function doInbox(req,res) {
|
|
|
61
62
|
logger.error(`tried method ${req.method} on inbox : forbidden`);
|
|
62
63
|
res.writeHead(403);
|
|
63
64
|
res.end('Forbidden');
|
|
64
|
-
return;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
package/lib/util.js
CHANGED
|
@@ -67,7 +67,7 @@ async function sendNotification(url,json,options) {
|
|
|
67
67
|
'Accept': 'application/json',
|
|
68
68
|
'Content-Type': 'application/json'
|
|
69
69
|
},
|
|
70
|
-
body: JSON.stringify(json)
|
|
70
|
+
body: JSON.stringify(json,null,4)
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
if (! response.ok) {
|
|
@@ -125,11 +125,78 @@ function dynamic_handler(handler,fallback) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
function parseAsJSON(path) {
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(fs.readFileSync(path, { encoding: 'utf-8'}));
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
logger.error(`failed to parse ${path}`);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ldPropertyAsId(object_or_string) {
|
|
139
|
+
if (!object_or_string) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (typeof object_or_string === 'string' || object_or_string instanceof String) {
|
|
144
|
+
return object_or_string;
|
|
145
|
+
}
|
|
146
|
+
else if (typeof object_or_string === 'object' && object_or_string['id']) {
|
|
147
|
+
return object_or_string['id'];
|
|
148
|
+
}
|
|
149
|
+
else if (typeof object_or_string === 'object' && object_or_string['@id']) {
|
|
150
|
+
return object_or_string['@id'];
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseArtifact(url,options) {
|
|
158
|
+
logger.debug(`isArtifact(${url})?`);
|
|
159
|
+
const base = options['base'] ? options['base'] :
|
|
160
|
+
options['host'] && options['port'] ?
|
|
161
|
+
`http://${options['host']}:${options['port']}` :
|
|
162
|
+
'http://localhost:8000';
|
|
163
|
+
|
|
164
|
+
const public = options['public'];
|
|
165
|
+
|
|
166
|
+
const filePath = url.substring(base.length + 1);
|
|
167
|
+
|
|
168
|
+
const metaPath = `${public}/${filePath}.meta`;
|
|
169
|
+
|
|
170
|
+
logger.debug(`searching ${metaPath}`);
|
|
171
|
+
|
|
172
|
+
if (! fs.existsSync(metaPath)) {
|
|
173
|
+
logger.debug(`no such file ${metaPath}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const json = parseAsJSON(metaPath);
|
|
178
|
+
|
|
179
|
+
if (json === null) {
|
|
180
|
+
logger.debug(`no json at ${metaPath}`);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (json['X-Artifact']) {
|
|
185
|
+
return `${public}/${filePath}`;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
128
192
|
module.exports = {
|
|
129
193
|
getLogger ,
|
|
130
194
|
backOff_fetch ,
|
|
131
195
|
fetchOriginal ,
|
|
132
196
|
moveTo ,
|
|
133
197
|
sendNotification ,
|
|
134
|
-
dynamic_handler
|
|
198
|
+
dynamic_handler ,
|
|
199
|
+
parseAsJSON ,
|
|
200
|
+
parseArtifact ,
|
|
201
|
+
ldPropertyAsId
|
|
135
202
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldn-inbox-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "A demonstration Event Notifications Inbox server",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"author": "Patrick Hochstenbach <Patrick.Hochstenbach@UGent.be>",
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"server": "npx ldn-inbox-server start-server",
|
|
10
10
|
"demo-post": "curl -X POST -H 'Content-Type: application/ld+json' --data-binary '@examples/offer.jsonld' http://localhost:8000/inbox/",
|
|
11
|
-
"handle-inbox": "npx ldn-inbox-server handler @inbox -hn ./handler/
|
|
12
|
-
"handle-outbox": "npx ldn-inbox-server handler @outbox",
|
|
11
|
+
"handle-inbox": "npx ldn-inbox-server handler @inbox -hn ./handler/accept_notification_handler.js",
|
|
12
|
+
"handle-outbox": "npx ldn-inbox-server handler @outbox -hn ./handler/send_notification_handler.js",
|
|
13
13
|
"handle-eventlog": "npx ldn-inbox-server handler @inbox -hn handler/eventlog_notification_handler.js",
|
|
14
|
-
"
|
|
14
|
+
"handle-inbox-multi": "npx ldn-inbox-server handler @inbox -hn ./handler/multi_notification_handler.js",
|
|
15
|
+
"handle-outbox-multi": "npx ldn-inbox-server handler @outbox -hn ./handler/multi_notification_handler.js",
|
|
16
|
+
"clean": "rm -rf error/* inbox/* outbox/* public/events/* public/events/log/* public/artifacts/artifact1 public/artifacts/artifact2 public/artifacts/*.jsonld*"
|
|
15
17
|
},
|
|
16
18
|
"bin": "./bin/ldn-inbox-server.js",
|
|
17
19
|
"keywords": [
|
|
@@ -25,10 +27,11 @@
|
|
|
25
27
|
"exponential-backoff": "^3.1.1",
|
|
26
28
|
"jsonschema": "^1.4.1",
|
|
27
29
|
"md5": "^2.3.0",
|
|
28
|
-
"mellon-server": "^1.0.
|
|
30
|
+
"mellon-server": "^1.0.8",
|
|
29
31
|
"node-fetch": "1.7.3",
|
|
30
32
|
"piscina": "^4.4.0",
|
|
31
33
|
"proper-lockfile": "^4.1.2",
|
|
34
|
+
"upath": "^2.0.1",
|
|
32
35
|
"uuid": "^9.0.1"
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<link
|
|
4
|
+
rel="stylesheet"
|
|
5
|
+
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
6
|
+
/>
|
|
7
|
+
<title>Artifact 1</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="container">
|
|
11
|
+
<h1>Artifact 1</h1>
|
|
12
|
+
<p>
|
|
13
|
+
<a href="artifact1.jsonld">Event Log</a>
|
|
14
|
+
</p>
|
|
15
|
+
</main>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<link
|
|
4
|
+
rel="stylesheet"
|
|
5
|
+
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
6
|
+
/>
|
|
7
|
+
<title>Artifact 2</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="container">
|
|
11
|
+
<h1>Artifact 2</h1>
|
|
12
|
+
<p>
|
|
13
|
+
<a href="artifact1.jsonld">Event Log</a>
|
|
14
|
+
</p>
|
|
15
|
+
</main>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|