Skip to content

Commit 91a074d

Browse files
committed
feat: PREP Notifications
Implements Per Resource Events notifications in Node Solid Server. Implementation Notes: + Uses `--experimental-require-module` to load esm packages natively. Requires node > 22.0.0. Start scripts and test invocations have been appropriately modified. + NSS converts strings into an older streaming format which was not being detected by Express-PREP. Express-PREP was modified to ask the user if the body provided is a stream, thus circumventing this issue. + Notifications are triggered from a common middleware, which is invoked after the response has been succesfully sent. + Uses Express PREP supplied default template for notifications.
1 parent f5b2b5c commit 91a074d

File tree

8 files changed

+366
-31
lines changed

8 files changed

+366
-31
lines changed

bin/solid

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
#!/usr/bin/env node
1+
#!/usr/bin/env -S node --experimental-require-module
22
const startCli = require('./lib/cli')
33
startCli()

bin/solid.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
#!/usr/bin/env node
1+
#!/usr/bin/env -S node --experimental-require-module
22
const startCli = require('./lib/cli')
33
startCli()

lib/create-app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ const ResourceMapper = require('./resource-mapper')
2828
const aclCheck = require('@solid/acl-check')
2929
const { version } = require('../package.json')
3030

31+
const acceptEvents = require('express-accept-events').default
32+
const events = require('express-events-negotiate').default
33+
const eventID = require('express-prep/event-id').default
34+
const prep = require('express-prep').default
35+
3136
const corsSettings = cors({
3237
methods: [
3338
'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE'
@@ -61,6 +66,9 @@ function createApp (argv = {}) {
6166

6267
const app = express()
6368

69+
// Add PREP support
70+
app.use(acceptEvents, events, eventID, prep)
71+
6472
initAppLocals(app, argv, ldp)
6573
initHeaders(app)
6674
initViews(app, configPath)

lib/handlers/get.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const translate = require('../utils.js').translate
1717
const error = require('../http-error')
1818

1919
const RDFs = require('../ldp').mimeTypesAsArray()
20+
const isRdf = require('../ldp').mimeTypeIsRdf
2021

2122
async function handler (req, res, next) {
2223
const ldp = req.app.locals.ldp
@@ -110,15 +111,39 @@ async function handler (req, res, next) {
110111
}
111112

112113
// If request accepts the content-type we found
114+
// if (stream && negotiator.mediaType([contentType])) {
115+
// res.setHeader('Content-Type', contentType)
116+
// if (contentRange) {
117+
// const headers = { 'Content-Range': contentRange, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize }
118+
// res.writeHead(206, headers)
119+
// return stream.pipe(res)
120+
// } else {
121+
// return stream.pipe(res)
122+
// }
123+
// }
124+
113125
if (stream && negotiator.mediaType([contentType])) {
114-
res.setHeader('Content-Type', contentType)
126+
let headers = {
127+
'Content-Type': contentType
128+
}
115129
if (contentRange) {
116-
const headers = { 'Content-Range': contentRange, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize }
117-
res.writeHead(206, headers)
118-
return stream.pipe(res)
119-
} else {
120-
return stream.pipe(res)
130+
headers = {
131+
...headers,
132+
'Content-Range': contentRange,
133+
'Accept-Ranges': 'bytes',
134+
'Content-Length': chunksize
135+
}
136+
res.statusCode = 206
121137
}
138+
139+
if (isRdf(contentType) && !res.sendEvents({
140+
config: { prep: '' },
141+
body: stream,
142+
isBodyStream: true,
143+
headers
144+
})) return
145+
res.set(headers)
146+
return stream.pipe(res)
122147
}
123148

124149
// If it is not in our RDFs we can't even translate,
@@ -130,6 +155,14 @@ async function handler (req, res, next) {
130155
// Translate from the contentType found to the possibleRDFType desired
131156
const data = await translate(stream, baseUri, contentType, possibleRDFType)
132157
debug(req.originalUrl + ' translating ' + contentType + ' -> ' + possibleRDFType)
158+
const headers = {
159+
'Content-Type': possibleRDFType
160+
}
161+
if (isRdf(contentType) && !res.sendEvents({
162+
config: { prep: '' },
163+
body: data,
164+
headers
165+
})) return
133166
res.setHeader('Content-Type', possibleRDFType)
134167
res.send(data)
135168
return next()

lib/handlers/notify.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = handler
2+
3+
function handler (req, res, next) {
4+
res.events.prep.trigger({
5+
generateNotifications () {
6+
return res.events.prep.defaultNotification({
7+
...(res.method === 'POST') && { location: res.getHeader('Content-Location') }
8+
})
9+
}
10+
})
11+
next()
12+
}

lib/ldp-middleware.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const del = require('./handlers/delete')
1010
const patch = require('./handlers/patch')
1111
const index = require('./handlers/index')
1212
const copy = require('./handlers/copy')
13+
const notify = require('./handlers/notify')
1314

1415
function LdpMiddleware (corsSettings) {
1516
const router = express.Router('/')
@@ -24,9 +25,9 @@ function LdpMiddleware (corsSettings) {
2425
router.copy('/*', allow('Write'), copy)
2526
router.get('/*', index, allow('Read'), header.addPermissions, get)
2627
router.post('/*', allow('Append'), post)
27-
router.patch('/*', allow('Append'), patch)
28+
router.patch('/*', allow('Append'), patch, notify)
2829
router.put('/*', allow('Append'), put)
29-
router.delete('/*', allow('Write'), del)
30+
router.delete('/*', allow('Write'), del, notify)
3031

3132
return router
3233
}

0 commit comments

Comments
 (0)