meyfa/selena-ide

View on GitHub
src/util/blob-stream.ts

Summary

Maintainability
A
0 mins
Test Coverage
// eslint-disable-next-line
import { EventEmitter } from 'events'

/**
 * A WritableStream that collects any chunks written to it.
 * After all data has been written, it can be converted into a Blob or a blob URL.
 */
export interface BlobStream extends NodeJS.WritableStream {
  toBlob: (type: string) => Blob
  toBlobURL: (type: string) => string
}

type StreamCallback = (err?: Error | null) => void

function isCallback (foo: unknown): foo is StreamCallback {
  return typeof foo === 'function'
}

function encode (data: string | Uint8Array, encoding?: BufferEncoding): Uint8Array {
  if (data instanceof Uint8Array) {
    return data
  }
  if (encoding != null && !/^utf-?8$/i.test(encoding)) {
    throw new Error('This stream only supports UTF-8.')
  }
  return new TextEncoder().encode(data)
}

class BlobStreamImpl extends EventEmitter implements BlobStream {
  readonly writable = true

  private readonly chunks: Uint8Array[] = []
  private blob: Blob | undefined

  private handleError (err: unknown, cb?: (err?: (Error | null)) => void): void {
    if (cb != null) {
      this.once('error', cb)
    }
    this.emit('error', err)
  }

  write (arg0: Uint8Array | string, arg1?: ((err?: (Error | null)) => void) | BufferEncoding, arg2?: (err?: (Error | null)) => void): boolean {
    const cb = [arg1, arg2].find(isCallback)
    try {
      this.chunks.push(encode(arg0, typeof arg1 === 'string' ? arg1 : undefined))
    } catch (err) {
      this.handleError(err, cb)
      return false
    }
    if (cb != null) {
      cb()
    }
    return true
  }

  end (arg0?: (() => void) | string | Uint8Array, arg1?: (() => void) | BufferEncoding, arg2?: () => void): this {
    if (arg0 instanceof Uint8Array || typeof arg0 === 'string') {
      // This will also invoke the callback for us.
      this.write(arg0, arg1, arg2)
    } else {
      const cb = [arg0, arg1, arg2].find(isCallback)
      if (cb != null) {
        cb()
      }
    }
    this.emit('finish')
    this.emit('close')
    return this
  }

  toBlob (type = 'application/octet-stream'): Blob {
    if (this.blob == null) {
      this.blob = new Blob(this.chunks, { type })
      // free memory
      this.chunks.splice(0, this.chunks.length)
    }
    if (this.blob.type !== type) {
      this.blob = new Blob([this.blob], { type })
    }
    return this.blob
  }

  toBlobURL (type: string): string {
    return URL.createObjectURL(this.toBlob(type))
  }
}

export function blobStream (): BlobStream {
  return new BlobStreamImpl()
}