Skip to content
This repository was archived by the owner on Apr 29, 2020. It is now read-only.

Commit 1baa183

Browse files
committed
chore: refactor to async/await
BREAKING CHANGE: This module used to export a class that extended EventEmitter, now it exports a function that returns an async iterable. I also updated the deps to use the latest http api, though it's removed the ability to add whole paths at once, along with some special logic to handle symlinks. The `Dicer` module that this module depends on will still emit events for when it encounters symlinks so I left the handlers in though am unsure if we actually use them.
1 parent edc2f72 commit 1baa183

File tree

7 files changed

+321
-325
lines changed

7 files changed

+321
-325
lines changed

README.md

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,30 @@ npm install ipfs-multipart
2727
## Usage
2828
```javascript
2929
const http = require('http')
30-
const IPFSMultipart = require('ipfs-multipart')
30+
const parser = require('ipfs-multipart')
3131

32-
http.createServer((req, res) => {
32+
http.createServer(async (req, res) => {
3333
if (req.method === 'POST' && req.headers['content-type']) {
34-
const parser = IPFSMultipart.reqParser(req)
3534

36-
parser.on('file', (fileName, fileStream) => {
37-
console.log(`file ${fileName} start`)
35+
for await (const entry of parser(req)) {
36+
if (entry.type === 'directory') {
37+
console.log(`dir ${entry.name} start`)
38+
}
3839

39-
fileStream.on('data', (data) => {
40-
console.log(`file ${fileName} contents:`, data.toString())
41-
})
40+
if (entry.type === 'file') {
41+
console.log(`file ${entry.name} start`)
4242

43-
fileStream.on('end', (data) => {
44-
console.log(`file ${fileName} end`)
45-
})
46-
})
43+
for await (const data of entry.content) {
44+
console.log(`file ${entry.name} contents:`, data.toString())
45+
}
4746

48-
parser.on('end', () => {
49-
console.log('finished parsing')
50-
res.writeHead(200)
51-
res.end()
52-
})
47+
console.log(`file ${fileName} end`)
48+
}
49+
}
5350

54-
return
51+
console.log('finished parsing')
52+
res.writeHead(200)
53+
res.end()
5554
}
5655

5756
res.writeHead(404)

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
},
2828
"dependencies": {
2929
"@hapi/content": "^4.1.0",
30-
"dicer": "~0.3.0"
30+
"dicer": "~0.3.0",
31+
"event-iterator": "^1.2.0"
3132
},
3233
"devDependencies": {
3334
"aegir": "^20.0.0",
3435
"chai": "^4.2.0",
35-
"ipfs-api": "github:ipfs/js-ipfs-api#1fd9749",
36+
"ipfs-api": "^26.1.2",
3637
"request": "^2.88.0"
3738
},
3839
"engines": {

src/index.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
'use strict'
22

33
const content = require('@hapi/content')
4-
const Parser = require('./parser')
4+
const parser = require('./parser')
55

6-
module.exports = {
7-
Parser,
8-
/**
9-
* Request Parser
10-
*
11-
* @param {Object} req - Request
12-
* @returns {Parser}
13-
*/
14-
reqParser: (req) => {
15-
const boundary = content.type(req.headers['content-type']).boundary
16-
const parser = new Parser({ boundary: boundary })
17-
req.pipe(parser)
18-
return parser
6+
/**
7+
* Request Parser
8+
*
9+
* @param {Object} req - Request
10+
* @returns {Object} an async iterable
11+
*/
12+
module.exports = async function * (req) {
13+
const boundary = content.type(req.headers['content-type']).boundary
14+
15+
for await (const entry of parser(boundary, req.payload || req)) {
16+
yield entry
1917
}
2018
}

src/parser.js

Lines changed: 71 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,32 @@
22

33
const Dicer = require('dicer')
44
const Content = require('@hapi/content')
5-
const stream = require('stream')
6-
const util = require('util')
7-
const Transform = stream.Transform
5+
const {
6+
EventIterator
7+
} = require('event-iterator')
88

99
const multipartFormdataType = 'multipart/form-data'
1010
const applicationDirectory = 'application/x-directory'
1111
const applicationSymlink = 'application/symlink'
1212

13+
function subscribe (emitter, dataEvent = 'data', endEvent = 'end', evOptions = {}) {
14+
return new EventIterator((push, stop, fail) => {
15+
emitter.addListener(dataEvent, push)
16+
emitter.addListener(endEvent, stop)
17+
emitter.addListener('error', fail)
18+
}, (push, stop, fail) => {
19+
emitter.removeListener(dataEvent, push)
20+
emitter.removeListener(endEvent, stop)
21+
emitter.removeListener('error', fail)
22+
23+
if (emitter.destroy) {
24+
emitter.destroy()
25+
} else if (typeof emitter.close === 'function') {
26+
emitter.close()
27+
}
28+
}, evOptions)
29+
}
30+
1331
const isDirectory = (mediatype) => mediatype === multipartFormdataType || mediatype === applicationDirectory
1432

1533
const parseDisposition = (disposition) => {
@@ -29,75 +47,61 @@ const parseHeader = (header) => {
2947
const disposition = parseDisposition(header['content-disposition'][0])
3048

3149
const details = type
32-
details.name = disposition.name
50+
details.name = decodeURIComponent(disposition.name)
3351
details.type = disposition.type
3452

3553
return details
3654
}
3755

38-
/**
39-
* Parser
40-
*
41-
* @constructor
42-
* @param {Object} options
43-
* @returns {Parser}
44-
*/
45-
function Parser (options) {
46-
// allow use without new
47-
if (!(this instanceof Parser)) {
48-
return new Parser(options)
49-
}
50-
51-
this.dicer = new Dicer({ boundary: options.boundary })
52-
53-
this.dicer.on('part', (part) => this.handlePart(part))
54-
55-
this.dicer.on('error', (err) => this.emit('err', err))
56-
57-
this.dicer.on('finish', () => {
58-
this.emit('finish')
59-
this.emit('end')
60-
})
61-
62-
Transform.call(this, options)
63-
}
64-
util.inherits(Parser, Transform)
65-
66-
Parser.prototype._transform = function (chunk, enc, cb) {
67-
this.dicer.write(chunk, enc)
68-
cb()
69-
}
70-
71-
Parser.prototype._flush = function (cb) {
72-
this.dicer.end()
73-
cb()
74-
}
75-
76-
Parser.prototype.handlePart = function (part) {
77-
part.on('header', (header) => {
78-
const partHeader = parseHeader(header)
79-
80-
if (isDirectory(partHeader.mime)) {
81-
part.on('data', () => false)
82-
this.emit('directory', partHeader.name)
83-
return
56+
async function * parser (boundary, stream) {
57+
const dicer = new Dicer({ boundary })
58+
stream.pipe(dicer)
59+
60+
for await (const part of subscribe(dicer, 'part', 'finish')) {
61+
for await (const header of subscribe(part, 'header')) {
62+
const partHeader = parseHeader(header)
63+
64+
if (isDirectory(partHeader.mime)) {
65+
// consume data from stream so we move on to the next header/part
66+
part.on('data', () => {})
67+
68+
yield {
69+
type: 'directory',
70+
name: partHeader.name
71+
}
72+
73+
continue
74+
}
75+
76+
if (partHeader.mime === applicationSymlink) {
77+
// consume data from stream so we move on to the next header/part
78+
part.on('data', () => {})
79+
80+
yield {
81+
type: 'symlink',
82+
name: partHeader.name,
83+
target: partHeader.target
84+
}
85+
86+
continue
87+
}
88+
89+
if (partHeader.boundary) {
90+
// recursively parse nested multiparts
91+
for await (const entry of parser(partHeader.boundary, part)) {
92+
yield entry
93+
}
94+
95+
continue
96+
}
97+
98+
yield {
99+
type: 'file',
100+
name: partHeader.name,
101+
content: subscribe(part)
102+
}
84103
}
85-
86-
if (partHeader.mime === applicationSymlink) {
87-
part.on('data', (target) => this.emit('symlink', partHeader.name, target.toString()))
88-
return
89-
}
90-
91-
if (partHeader.boundary) {
92-
// recursively parse nested multiparts
93-
const parser = new Parser({ boundary: partHeader.boundary })
94-
parser.on('file', (file) => this.emit('file', file))
95-
part.pipe(parser)
96-
return
97-
}
98-
99-
this.emit('file', partHeader.name, part)
100-
})
104+
}
101105
}
102106

103-
module.exports = Parser
107+
module.exports = parser

test/node.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)