github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/ui/app/utils/classes/stream-logger.js (about)

     1  import Ember from 'ember';
     2  import { task } from 'ember-concurrency';
     3  import TextDecoder from 'nomad-ui/utils/classes/text-decoder';
     4  import AbstractLogger from './abstract-logger';
     5  
     6  const { Object: EmberObject, computed } = Ember;
     7  
     8  export default EmberObject.extend(AbstractLogger, {
     9    reader: null,
    10  
    11    additionalParams: computed(() => ({
    12      follow: true,
    13    })),
    14  
    15    start() {
    16      return this.get('poll').perform();
    17    },
    18  
    19    stop() {
    20      const reader = this.get('reader');
    21      if (reader) {
    22        reader.cancel();
    23      }
    24      return this.get('poll').cancelAll();
    25    },
    26  
    27    poll: task(function*() {
    28      const url = this.get('fullUrl');
    29      const logFetch = this.get('logFetch');
    30  
    31      let streamClosed = false;
    32      let buffer = '';
    33  
    34      const decoder = new TextDecoder();
    35      const reader = yield logFetch(url).then(res => res.body.getReader());
    36  
    37      this.set('reader', reader);
    38  
    39      while (!streamClosed) {
    40        yield reader.read().then(({ value, done }) => {
    41          streamClosed = done;
    42  
    43          // There is no guarantee that value will be a complete JSON object,
    44          // so it needs to be buffered.
    45          buffer += decoder.decode(value, { stream: true });
    46  
    47          // Only when the buffer contains a close bracket can we be sure the buffer
    48          // is in a complete state
    49          if (buffer.indexOf('}') !== -1) {
    50            // The buffer can be one or more complete frames with additional text for the
    51            // next frame
    52            const [, chunk, newBuffer] = buffer.match(/(.*\})(.*)$/);
    53  
    54            // Peel chunk off the front of the buffer (since it represents complete frames)
    55            // and set the buffer to be the remainder
    56            buffer = newBuffer;
    57  
    58            // Assuming the logs endpoint never returns nested JSON (it shouldn't), at this
    59            // point chunk is a series of valid JSON objects with no delimiter.
    60            const lines = chunk.replace(/\}\{/g, '}\n{').split('\n');
    61            const frames = lines.map(line => JSON.parse(line)).filter(frame => frame.Data);
    62  
    63            if (frames.length) {
    64              frames.forEach(frame => (frame.Data = window.atob(frame.Data)));
    65              this.set('endOffset', frames[frames.length - 1].Offset);
    66              this.get('write')(frames.mapBy('Data').join(''));
    67            }
    68          }
    69        });
    70      }
    71    }),
    72  }).reopenClass({
    73    isSupported: !!window.ReadableStream,
    74  });