From fcac981355077f9dbd8f463d2a97b77db9e17138 Mon Sep 17 00:00:00 2001 From: nlf Date: Thu, 19 May 2022 08:53:22 -0700 Subject: [PATCH] fix: store emitted events and re-emit them for late listeners --- lib/index.js | 27 ++++++++++++++++- test/integrity-stream.js | 65 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index e2732fd..1443137 100644 --- a/lib/index.js +++ b/lib/index.js @@ -30,6 +30,10 @@ const getOptString = options => !options || !options.length const _onEnd = Symbol('_onEnd') const _getOptions = Symbol('_getOptions') +const _emittedSize = Symbol('_emittedSize') +const _emittedIntegrity = Symbol('_emittedIntegrity') +const _emittedVerified = Symbol('_emittedVerified') + class IntegrityStream extends MiniPass { constructor (opts) { super() @@ -63,6 +67,22 @@ class IntegrityStream extends MiniPass { this.optString = getOptString(options) } + on (ev, handler) { + if (ev === 'size' && this[_emittedSize]) { + return handler(this[_emittedSize]) + } + + if (ev === 'integrity' && this[_emittedIntegrity]) { + return handler(this[_emittedIntegrity]) + } + + if (ev === 'verified' && this[_emittedVerified]) { + return handler(this[_emittedVerified]) + } + + return super.on(ev, handler) + } + emit (ev, data) { if (ev === 'end') { this[_onEnd]() @@ -103,9 +123,14 @@ class IntegrityStream extends MiniPass { err.sri = this.sri this.emit('error', err) } else { + this[_emittedSize] = this.size this.emit('size', this.size) + this[_emittedIntegrity] = newSri this.emit('integrity', newSri) - match && this.emit('verified', match) + if (match) { + this[_emittedVerified] = match + this.emit('verified', match) + } } } } diff --git a/test/integrity-stream.js b/test/integrity-stream.js index 5f45faa..d4335ba 100644 --- a/test/integrity-stream.js +++ b/test/integrity-stream.js @@ -4,10 +4,28 @@ const test = require('tap').test const ssri = require('..') -test('generates integrity', t => { +test('works with no options', t => { const TARGET = ssri.fromData('foo') const stream = ssri.integrityStream() stream.write('foo') + let integrity + stream.on('integrity', i => { + integrity = i + }) + + stream.on('end', () => { + t.same(integrity, TARGET, 'matching integrity emitted') + t.end() + }) + + stream.resume() + stream.end() +}) + +test('generates integrity', t => { + const TARGET = ssri.fromData('foo') + const stream = ssri.integrityStream({ integrity: TARGET }) + stream.write('foo') let collected = '' stream.on('data', d => { collected += d.toString() @@ -16,9 +34,54 @@ test('generates integrity', t => { stream.on('integrity', i => { integrity = i }) + let size + stream.on('size', s => { + size = s + }) + let verified + stream.on('verified', v => { + verified = v + }) + stream.on('end', () => { + t.equal(collected, 'foo', 'stream output is complete') + t.equal(size, 3, 'size emitted') + t.same(integrity, TARGET, 'matching integrity emitted') + t.same(verified, TARGET.sha512[0], 'verified emitted') + t.end() + }) + stream.end() +}) + +test('re-emits for late listeners', t => { + const TARGET = ssri.fromData('foo') + const stream = ssri.integrityStream({ integrity: TARGET }) + stream.write('foo') + let collected = '' + stream.on('data', d => { + collected += d.toString() + }) + stream.on('end', () => { + // we add the listeners _after_ the end event this time to ensure that the events + // get emitted again for late listeners + let integrity + stream.on('integrity', i => { + integrity = i + }) + + let size + stream.on('size', s => { + size = s + }) + + let verified + stream.on('verified', v => { + verified = v + }) t.equal(collected, 'foo', 'stream output is complete') + t.equal(size, 3, 'size emitted') t.same(integrity, TARGET, 'matching integrity emitted') + t.same(verified, TARGET.sha512[0], 'verified emitted') t.end() }) stream.end()