<commit_before>import logger from '../loggly/logger'
import {
  APIError,
  InternalServerError,
  NotFoundAPIError
} from './APIError'
import ws from 'ws'

import { URL } from 'url'
import uid from 'uid-safe'
import Authentication from './Authentication'
import Permission from './Permission'
import Meta from './Meta'

const PRETTY_PRINT_SPACING = 2

const apiEvents = [
  'rescueCreated',
  'rescueUpdated',
  'rescueDeleted',
  'ratCreated',
  'ratUpdated',
  'ratDeleted',
  'userCreated',
  'userUpdated',
  'userDeleted',
  'shipCreated',
  'shipUpdated',
  'shipDeleted',
  'connection'
]

const routes = {}

export default class WebSocket {
  constructor (server, trafficManager) {
    this.wss = new ws.Server({server})
    this.traffic = trafficManager

    this.wss.on('connection', async (client, req) => {
      let url = new URL(`http://localhost:8082${req.url}`)
      client.req = req
      client.clientId = uid.sync(GLOBAL.WEBSOCKET_IDENTIFIER_ROUNDS)
      client.subscriptions = []

      let bearer = url.searchParams.get('bearer')
      if (bearer) {
        let {user, scope} = await Authentication.bearerAuthenticate(bearer)
        if (user) {
          client.user = user
          client.scope = scope
        }
      }

      this.onConnection(client)

      client.on('message', (message) => {
        try {
          let request = JSON.parse(String(message))
          this.onMessage(client, request)
        } catch (ex) {
          logger.info('Failed to parse incoming websocket message')
        }
      })

      for (let event of apiEvents) {
        process.on(event, (ctx, result, permissions) => {
          this.onEvent.call(this, event, ctx, result, permissions)
        })
      }

      process.on('apiBroadcast', (id, ctx, result) => {
        this.onBroadcast.call(this, id, ctx, result)
      })
    })
  }

  async onConnection (client) {
    let ctx = new Context(client, {})
    let route = await WebSocket.getRoute('version', 'read')
    let result = await route(ctx)
    let meta = {
      event: 'connection'
    }

    let rateLimit = this.traffic.validateRateLimit(ctx, false)
    Object.assign(meta, {
      'API-Version': 'v2.1',
      'Rate-Limit-Limit': rateLimit.total,
      'Rate-Limit-Remaining': rateLimit.remaining,
      'Rate-Limit-Reset':  this.traffic.nextResetDate
    })
    this.send(client, { result:  result.data, meta: meta })
  }

  async onMessage (client, request) {
    try {
      let { result, meta } = await this.route(client, request)
      if (!result.meta) {
        result.meta = {}
      }
      Object.assign(result.meta, meta)
      this.send(client, result)
    } catch (ex) {
      let error = ex
      if ((error instanceof APIError) === false) {
        error = new InternalServerError({})
      }
      this.send(client, error)
    }
  }

  async route (client, request) {
    let ctx = new Context(client, request)

    let rateLimit = this.traffic.validateRateLimit(ctx)

    let meta = Object.assign(request.meta || {}, {
      'API-Version': 'v2.1',
      'Rate-Limit-Limit': rateLimit.total,
      'Rate-Limit-Remaining': rateLimit.remaining,
      'Rate-Limit-Reset':  this.traffic.nextResetDate
    })

    let [endpointName, methodName] = request.action || []
    let route = WebSocket.getRoute(endpointName, methodName)
    let result = await route(ctx)

    return { result:  result, meta: meta }
  }

  onBroadcast (id, ctx, result) {
    let clients = [...this.socket.clients].filter((client) => {
      return client.subscriptions.includes(id)
    })
    this.broadcast(clients, result)
  }

  onEvent (event, ctx, result, permissions = null) {
    let clients = [...this.socket.clients].filter((client) => {
      if (client.clientId !== ctx.client.clientId) {
        return (!permissions || Permission.granted(permissions, client.user, client.scope))
      }
      return false
    })
    if (!result.meta) {
      result.meta = {}
    }

    Object.assign(result.meta, { event })
    this.broadcast(clients, result)
  }

  send (client, message) {
    try {
      if (process.env.NODE_ENV === 'production') {
        client.send(JSON.stringify(message))
      } else {
        client.send(JSON.stringify(message, null, PRETTY_PRINT_SPACING))
      }
    } catch (ex) {
      logger.info('Failed to send websocket message')
    }
  }

  broadcast (clients, message) {
    for (let client of clients) {
      this.send(client, message)
    }
  }

  static addRoute (endpointName, methodName, method) {
    if (routes.hasOwnProperty(endpointName) === false) {
      routes[endpointName] = {}
    }

    routes[endpointName][methodName] = method
  }

  static getRoute (endpointName, methodName) {
    if (routes.hasOwnProperty(endpointName) === false || routes[endpointName].hasOwnProperty(methodName) === false) {
      throw NotFoundAPIError({ parameter: 'action' })
    }
    return routes[endpointName][methodName]
  }
}

export class Context {
  constructor (client, request) {
    this.inet = client.req.headers['X-Forwarded-for'] || client.req.connection.remoteAddress

    this.client = client
    this.state = {}
    this.state.scope = client.scope
    this.state.user = client.user

    this.query = {}
    Object.assign(this.query, request)
    this.meta = new Meta()
    Object.assign(this.meta, this.query)
    this.data = request.data

    delete this.query.data
    delete this.query.meta
    delete this.query.action
    this.params = this.query
  }
}

/**
 * ESNext Decorator for routing this method for websocket requests
 * @param endpointName The endpoint name to route websocket requests for
 * @param methodName The method name to route websocket requests for
 * @returns {Function} An ESNext decorator function
 */
export function websocket (endpointName, methodName) {
  return function (target, name, descriptor) {
    WebSocket.addRoute(endpointName, methodName, descriptor.value)
  }
}
<commit_msg>Return meta field with error<commit_after>import logger from '../loggly/logger'
import {
  APIError,
  InternalServerError,
  NotFoundAPIError
} from './APIError'
import ws from 'ws'

import { URL } from 'url'
import uid from 'uid-safe'
import Authentication from './Authentication'
import Permission from './Permission'
import Meta from './Meta'

const PRETTY_PRINT_SPACING = 2

const apiEvents = [
  'rescueCreated',
  'rescueUpdated',
  'rescueDeleted',
  'ratCreated',
  'ratUpdated',
  'ratDeleted',
  'userCreated',
  'userUpdated',
  'userDeleted',
  'shipCreated',
  'shipUpdated',
  'shipDeleted',
  'connection'
]

const routes = {}

export default class WebSocket {
  constructor (server, trafficManager) {
    this.wss = new ws.Server({server})
    this.traffic = trafficManager

    this.wss.on('connection', async (client, req) => {
      let url = new URL(`http://localhost:8082${req.url}`)
      client.req = req
      client.clientId = uid.sync(GLOBAL.WEBSOCKET_IDENTIFIER_ROUNDS)
      client.subscriptions = []

      let bearer = url.searchParams.get('bearer')
      if (bearer) {
        let {user, scope} = await Authentication.bearerAuthenticate(bearer)
        if (user) {
          client.user = user
          client.scope = scope
        }
      }

      this.onConnection(client)

      client.on('message', (message) => {
        try {
          let request = JSON.parse(String(message))
          this.onMessage(client, request)
        } catch (ex) {
          logger.info('Failed to parse incoming websocket message')
        }
      })

      for (let event of apiEvents) {
        process.on(event, (ctx, result, permissions) => {
          this.onEvent.call(this, event, ctx, result, permissions)
        })
      }

      process.on('apiBroadcast', (id, ctx, result) => {
        this.onBroadcast.call(this, id, ctx, result)
      })
    })
  }

  async onConnection (client) {
    let ctx = new Context(client, {})
    let route = await WebSocket.getRoute('version', 'read')
    let result = await route(ctx)
    let meta = {
      event: 'connection'
    }

    let rateLimit = this.traffic.validateRateLimit(ctx, false)
    Object.assign(meta, {
      'API-Version': 'v2.1',
      'Rate-Limit-Limit': rateLimit.total,
      'Rate-Limit-Remaining': rateLimit.remaining,
      'Rate-Limit-Reset':  this.traffic.nextResetDate
    })
    this.send(client, { result:  result.data, meta: meta })
  }

  async onMessage (client, request) {
    try {
      let { result, meta } = await this.route(client, request)
      if (!result.meta) {
        result.meta = {}
      }
      Object.assign(result.meta, meta)
      this.send(client, result)
    } catch (ex) {
      let error = ex
      if ((error instanceof APIError) === false) {
        error = new InternalServerError({})
      }
      this.send(client, Object.assign({"meta": request.meta}, error))
    }
  }

  async route (client, request) {
    let ctx = new Context(client, request)

    let rateLimit = this.traffic.validateRateLimit(ctx)

    let meta = Object.assign(request.meta || {}, {
      'API-Version': 'v2.1',
      'Rate-Limit-Limit': rateLimit.total,
      'Rate-Limit-Remaining': rateLimit.remaining,
      'Rate-Limit-Reset':  this.traffic.nextResetDate
    })

    let [endpointName, methodName] = request.action || []
    let route = WebSocket.getRoute(endpointName, methodName)
    let result = await route(ctx)

    return { result:  result, meta: meta }
  }

  onBroadcast (id, ctx, result) {
    let clients = [...this.socket.clients].filter((client) => {
      return client.subscriptions.includes(id)
    })
    this.broadcast(clients, result)
  }

  onEvent (event, ctx, result, permissions = null) {
    let clients = [...this.socket.clients].filter((client) => {
      if (client.clientId !== ctx.client.clientId) {
        return (!permissions || Permission.granted(permissions, client.user, client.scope))
      }
      return false
    })
    if (!result.meta) {
      result.meta = {}
    }

    Object.assign(result.meta, { event })
    this.broadcast(clients, result)
  }

  send (client, message) {
    try {
      if (process.env.NODE_ENV === 'production') {
        client.send(JSON.stringify(message))
      } else {
        client.send(JSON.stringify(message, null, PRETTY_PRINT_SPACING))
      }
    } catch (ex) {
      logger.info('Failed to send websocket message')
    }
  }

  broadcast (clients, message) {
    for (let client of clients) {
      this.send(client, message)
    }
  }

  static addRoute (endpointName, methodName, method) {
    if (routes.hasOwnProperty(endpointName) === false) {
      routes[endpointName] = {}
    }

    routes[endpointName][methodName] = method
  }

  static getRoute (endpointName, methodName) {
    if (routes.hasOwnProperty(endpointName) === false || routes[endpointName].hasOwnProperty(methodName) === false) {
      throw NotFoundAPIError({ parameter: 'action' })
    }
    return routes[endpointName][methodName]
  }
}

export class Context {
  constructor (client, request) {
    this.inet = client.req.headers['X-Forwarded-for'] || client.req.connection.remoteAddress

    this.client = client
    this.state = {}
    this.state.scope = client.scope
    this.state.user = client.user

    this.query = {}
    Object.assign(this.query, request)
    this.meta = new Meta()
    Object.assign(this.meta, this.query)
    this.data = request.data

    delete this.query.data
    delete this.query.meta
    delete this.query.action
    this.params = this.query
  }
}

/**
 * ESNext Decorator for routing this method for websocket requests
 * @param endpointName The endpoint name to route websocket requests for
 * @param methodName The method name to route websocket requests for
 * @returns {Function} An ESNext decorator function
 */
export function websocket (endpointName, methodName) {
  return function (target, name, descriptor) {
    WebSocket.addRoute(endpointName, methodName, descriptor.value)
  }
}
<|endoftext|>


<commit_before>const test = require('tap').test;
const VirtualMachine = require('../../src/index');
const sb3 = require('../../src/serialization/sb3');
const demoSb3 = require('../fixtures/demo.json');

test('serialize', t => {
    const vm = new VirtualMachine();
    vm.loadProject(JSON.stringify(demoSb3))
        .then(() => {
            const result = sb3.serialize(vm.runtime);
            // @todo Analyze
            t.type(JSON.stringify(result), 'string');
            t.end();
        });
});

test('deserialize', t => {
    const vm = new VirtualMachine();
    sb3.deserialize('', vm.runtime).then(({targets}) => {
        // @todo Analyze
        t.type(targets, 'object');
        t.end();
    });
});
<commit_msg>Fixing tests<commit_after>const test = require('tap').test;
const path = require('path');
const VirtualMachine = require('../../src/index');
const sb3 = require('../../src/serialization/sb3');
const extract = require('../fixtures/extract');
const projectPath = path.resolve(__dirname, '../fixtures/clone-cleanup.sb2');

test('serialize', t => {
    const vm = new VirtualMachine();
    vm.loadProject(extract(projectPath))
        .then(() => {
            const result = sb3.serialize(vm.runtime);
            // @todo Analyze
            t.type(JSON.stringify(result), 'string');
            t.end();
        });
});

test('deserialize', t => {
    const vm = new VirtualMachine();
    sb3.deserialize('', vm.runtime).then(({targets}) => {
        // @todo Analyze
        t.type(targets, 'object');
        t.end();
    });
});
<|endoftext|>


<commit_before>var Gossiper = require('../lib/gossiper').Gossiper;
// Create a seed peer.
var seed = new Gossiper(9000, []);
seed.start();

// Create 20 new peers and point them at the seed (usually this would happen in 20 separate processes)
// To prevent having a single point of failure you would probably have multiple seeds
for(var i = 9001; i <= 9020;i++) {
  var g = new Gossiper(i, ['127.0.0.1:9000']);
  g.start();

  g.on('update', function(peer, k, v) {
    if(k == 'somekey') {
      console.log("peer " + peer + " set " + k + " to " + v); // peer 127.0.0.1:9999 set somekey to somevalue
    }
  });
}

// Add another peer which updates it's state after 15 seconds
var updater = new Gossiper(9999, ['127.0.0.1:9000']);
updater.start();
setTimeout(function() {
  updater.setLocalState('somekey', 'somevalue');
}, 15000);
<commit_msg>fixed this example to work<commit_after>var Gossiper = require('../lib/gossiper').Gossiper;
// Create a seed peer.
var seed = new Gossiper(9000, [], '127.0.0.1');
seed.start();

// Create 20 new peers and point them at the seed (usually this would happen in 20 separate processes)
// To prevent having a single point of failure you would probably have multiple seeds
for(var i = 9001; i <= 9020;i++) {
  var g = new Gossiper(i, ['127.0.0.1:9000']);
  g.start();

  g.on('update', function(peer, k, v) {
    if(k == 'somekey') {
      console.log("peer " + peer + " set " + k + " to " + v); // peer 127.0.0.1:9999 set somekey to somevalue
    }
  });
}

// Add another peer which updates it's state after 15 seconds
var updater = new Gossiper(9999, ['127.0.0.1:9000']);
updater.start();
setInterval(function() {
  updater.setLocalState('somekey', 'somevalue');
}, 15000);
<|endoftext|>


<commit_before>// ______________________
// MediaStreamRecorder.js

/**
 * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox.
 * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}.
 * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
 * @author {@link https://github.com/muaz-khan|Muaz Khan}
 * @typedef MediaStreamRecorder
 * @class
 * @example
 * var config = {
 *     mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis
 *     audioBitsPerSecond : 256 * 8 * 1024,
 *     videoBitsPerSecond : 256 * 8 * 1024,
 *     bitsPerSecond: 256 * 8 * 1024,  // if this is provided, skip above two
 *     checkForInactiveTracks: true,
 *     timeSlice: 1000, // concatenate intervals based blobs
 *     ondataavailable: function() {} // get intervals based blobs
 * }
 * var recorder = new MediaStreamRecorder(mediaStream, config);
 * recorder.record();
 * recorder.stop(function(blob) {
 *     video.src = URL.createObjectURL(blob);
 *
 *     // or
 *     var blob = recorder.blob;
 * });
 * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
 * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
 * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000}
 * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser.
 */

function MediaStreamRecorder(mediaStream, config) {
    var self = this;

    if (typeof mediaStream === 'undefined') {
        throw 'First argument "MediaStream" is required.';
    }

    if (typeof MediaRecorder === 'undefined') {
        throw 'Your browser does not supports Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.';
    }

    config = config || {
        // bitsPerSecond: 256 * 8 * 1024,
        mimeType: 'video/webm'
    };

    if (config.type === 'audio') {
        if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) {
            var stream;
            if (!!navigator.mozGetUserMedia) {
                stream = new MediaStream();
                stream.addTrack(getTracks(mediaStream, 'audio')[0]);
            } else {
                // webkitMediaStream
                stream = new MediaStream(getTracks(mediaStream, 'audio'));
            }
            mediaStream = stream;
        }

        if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) {
            config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg';
        }

        if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) {
            // forcing better codecs on Firefox (via #166)
            config.mimeType = 'audio/ogg';
        }
    }

    var arrayOfBlobs = [];

    /**
     * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var arrayOfBlobs = recorder.getArrayOfBlobs();
     * @returns {Array} Returns array of recorded blobs.
     */
    this.getArrayOfBlobs = function() {
        return arrayOfBlobs;
    };

    /**
     * This method records MediaStream.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.record();
     */
    this.record = function() {
        // set defaults
        self.blob = null;
        self.clearRecordedData();
        self.timestamps = [];
        allStates = [];
        arrayOfBlobs = [];

        var recorderHints = config;

        if (!config.disableLogs) {
            console.log('Passing following config over MediaRecorder API.', recorderHints);
        }

        if (mediaRecorder) {
            // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page.
            mediaRecorder = null;
        }

        if (isChrome && !isMediaRecorderCompatible()) {
            // to support video-only recording on stable
            recorderHints = 'video/vp8';
        }

        if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) {
            if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) {
                if (!config.disableLogs) {
                    console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
                }

                recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm';
            }
        }

        // using MediaRecorder API here
        try {
            mediaRecorder = new MediaRecorder(mediaStream, recorderHints);

            // reset
            config.mimeType = recorderHints.mimeType;
        } catch (e) {
            // chrome-based fallback
            mediaRecorder = new MediaRecorder(mediaStream);
        }

        // old hack?
        if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) {
            if (!config.disableLogs) {
                console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
            }
        }

        // Dispatching OnDataAvailable Handler
        mediaRecorder.ondataavailable = function(e) {
            if (e.data) {
                allStates.push('ondataavailable: ' + bytesToSize(e.data.size));
            }

            if (typeof config.timeSlice === 'number') {
                if (e.data && e.data.size && e.data.size > 100) {
                    arrayOfBlobs.push(e.data);
                    updateTimeStamp();

                    if (typeof config.ondataavailable === 'function') {
                        // intervals based blobs
                        var blob = config.getNativeBlob ? e.data : new Blob([e.data], {
                            type: getMimeType(recorderHints)
                        });
                        config.ondataavailable(blob);
                    }
                }
                return;
            }

            if (!e.data || !e.data.size || e.data.size < 100 || self.blob) {
                // make sure that stopRecording always getting fired
                // even if there is invalid data
                if (self.recordingCallback) {
                    self.recordingCallback(new Blob([], {
                        type: getMimeType(recorderHints)
                    }));
                    self.recordingCallback = null;
                }
                return;
            }

            self.blob = config.getNativeBlob ? e.data : new Blob([e.data], {
                type: getMimeType(recorderHints)
            });

            if (self.recordingCallback) {
                self.recordingCallback(self.blob);
                self.recordingCallback = null;
            }
        };

        mediaRecorder.onstart = function() {
            allStates.push('started');
        };

        mediaRecorder.onpause = function() {
            allStates.push('paused');
        };

        mediaRecorder.onresume = function() {
            allStates.push('resumed');
        };

        mediaRecorder.onstop = function() {
            allStates.push('stopped');
        };

        mediaRecorder.onerror = function(error) {
            if (!error) {
                return;
            }

            if (!error.name) {
                error.name = 'UnknownError';
            }

            allStates.push('error: ' + error);

            if (!config.disableLogs) {
                // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary
                if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) {
                    console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error);
                } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) {
                    console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error);
                } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) {
                    console.error('MediaRecorder security error', error);
                }

                // older code below
                else if (error.name === 'OutOfMemory') {
                    console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'IllegalStreamModification') {
                    console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'OtherRecordingError') {
                    console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'GenericError') {
                    console.error('The UA cannot provide the codec or recording option that has been requested.', error);
                } else {
                    console.error('MediaRecorder Error', error);
                }
            }

            (function(looper) {
                if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') {
                    delete config.timeslice;

                    // 10 minutes, enough?
                    mediaRecorder.start(10 * 60 * 1000);
                    return;
                }

                setTimeout(looper, 1000);
            })();

            if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') {
                mediaRecorder.stop();
            }
        };

        if (typeof config.timeSlice === 'number') {
            updateTimeStamp();
            mediaRecorder.start(config.timeSlice);
        } else {
            // default is 60 minutes; enough?
            // use config => {timeSlice: 1000} otherwise

            mediaRecorder.start(3.6e+6);
        }

        if (config.initCallback) {
            config.initCallback(); // old code
        }
    };

    /**
     * @property {Array} timestamps - Array of time stamps
     * @memberof MediaStreamRecorder
     * @example
     * console.log(recorder.timestamps);
     */
    this.timestamps = [];

    function updateTimeStamp() {
        self.timestamps.push(new Date().getTime());

        if (typeof config.onTimeStamp === 'function') {
            config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps);
        }
    }

    function getMimeType(secondObject) {
        if (mediaRecorder && mediaRecorder.mimeType) {
            return mediaRecorder.mimeType;
        }

        return secondObject.mimeType || 'video/webm';
    }

    /**
     * This method stops recording MediaStream.
     * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.stop(function(blob) {
     *     video.src = URL.createObjectURL(blob);
     * });
     */
    this.stop = function(callback) {
        callback = callback || function() {};

        self.manuallyStopped = true; // used inside the mediaRecorder.onerror

        if (!mediaRecorder) {
            return;
        }

        this.recordingCallback = callback;

        if (mediaRecorder.state === 'recording') {
            mediaRecorder.stop();
        }

        if (typeof config.timeSlice === 'number') {
            setTimeout(function() {
                self.blob = new Blob(arrayOfBlobs, {
                    type: getMimeType(config)
                });

                self.recordingCallback(self.blob);
            }, 100);
        }
    };

    /**
     * This method pauses the recording process.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.pause();
     */
    this.pause = function() {
        if (!mediaRecorder) {
            return;
        }

        if (mediaRecorder.state === 'recording') {
            mediaRecorder.pause();
        }
    };

    /**
     * This method resumes the recording process.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.resume();
     */
    this.resume = function() {
        if (!mediaRecorder) {
            return;
        }

        if (mediaRecorder.state === 'paused') {
            mediaRecorder.resume();
        }
    };

    /**
     * This method resets currently recorded data.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.clearRecordedData();
     */
    this.clearRecordedData = function() {
        if (mediaRecorder && mediaRecorder.state === 'recording') {
            self.stop(clearRecordedDataCB);
        }

        clearRecordedDataCB();
    };

    function clearRecordedDataCB() {
        arrayOfBlobs = [];
        mediaRecorder = null;
        self.timestamps = [];
    }

    // Reference to "MediaRecorder" object
    var mediaRecorder;

    /**
     * Access to native MediaRecorder API
     * @method
     * @memberof MediaStreamRecorder
     * @instance
     * @example
     * var internal = recorder.getInternalRecorder();
     * internal.ondataavailable = function() {}; // override
     * internal.stream, internal.onpause, internal.onstop, etc.
     * @returns {Object} Returns internal recording object.
     */
    this.getInternalRecorder = function() {
        return mediaRecorder;
    };

    function isMediaStreamActive() {
        if ('active' in mediaStream) {
            if (!mediaStream.active) {
                return false;
            }
        } else if ('ended' in mediaStream) { // old hack
            if (mediaStream.ended) {
                return false;
            }
        }
        return true;
    }

    /**
     * @property {Blob} blob - Recorded data as "Blob" object.
     * @memberof MediaStreamRecorder
     * @example
     * recorder.stop(function() {
     *     var blob = recorder.blob;
     * });
     */
    this.blob = null;

    /**
     * Get MediaRecorder readonly state.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var state = recorder.getState();
     * @returns {String} Returns recording state.
     */
    this.getState = function() {
        if (!mediaRecorder) {
            return 'inactive';
        }

        return mediaRecorder.state || 'inactive';
    };

    // list of all recording states
    var allStates = [];

    /**
     * Get MediaRecorder all recording states.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var state = recorder.getAllStates();
     * @returns {Array} Returns all recording states
     */
    this.getAllStates = function() {
        return allStates;
    };

    // if any Track within the MediaStream is muted or not enabled at any time,
    // the browser will only record black frames
    // or silence since that is the content produced by the Track
    // so we need to stopRecording as soon as any single track ends.
    if (typeof config.checkForInactiveTracks === 'undefined') {
        config.checkForInactiveTracks = false; // disable to minimize CPU usage
    }

    var self = this;

    // this method checks if media stream is stopped
    // or if any track is ended.
    (function looper() {
        if (!mediaRecorder || config.checkForInactiveTracks === false) {
            return;
        }

        if (isMediaStreamActive() === false) {
            if (!config.disableLogs) {
                console.log('MediaStream seems stopped.');
            }
            self.stop();
            return;
        }

        setTimeout(looper, 1000); // check every second
    })();

    // for debugging
    this.name = 'MediaStreamRecorder';
    this.toString = function() {
        return this.name;
    };
}

if (typeof RecordRTC !== 'undefined') {
    RecordRTC.MediaStreamRecorder = MediaStreamRecorder;
}
<commit_msg>fix typo<commit_after>// ______________________
// MediaStreamRecorder.js

/**
 * MediaStreamRecorder is an abstraction layer for {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}. It is used by {@link RecordRTC} to record MediaStream(s) in both Chrome and Firefox.
 * @summary Runs top over {@link https://w3c.github.io/mediacapture-record/MediaRecorder.html|MediaRecorder API}.
 * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
 * @author {@link https://github.com/muaz-khan|Muaz Khan}
 * @typedef MediaStreamRecorder
 * @class
 * @example
 * var config = {
 *     mimeType: 'video/webm', // vp8, vp9, h264, mkv, opus/vorbis
 *     audioBitsPerSecond : 256 * 8 * 1024,
 *     videoBitsPerSecond : 256 * 8 * 1024,
 *     bitsPerSecond: 256 * 8 * 1024,  // if this is provided, skip above two
 *     checkForInactiveTracks: true,
 *     timeSlice: 1000, // concatenate intervals based blobs
 *     ondataavailable: function() {} // get intervals based blobs
 * }
 * var recorder = new MediaStreamRecorder(mediaStream, config);
 * recorder.record();
 * recorder.stop(function(blob) {
 *     video.src = URL.createObjectURL(blob);
 *
 *     // or
 *     var blob = recorder.blob;
 * });
 * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
 * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
 * @param {object} config - {disableLogs:true, initCallback: function, mimeType: "video/webm", timeSlice: 1000}
 * @throws Will throw an error if first argument "MediaStream" is missing. Also throws error if "MediaRecorder API" are not supported by the browser.
 */

function MediaStreamRecorder(mediaStream, config) {
    var self = this;

    if (typeof mediaStream === 'undefined') {
        throw 'First argument "MediaStream" is required.';
    }

    if (typeof MediaRecorder === 'undefined') {
        throw 'Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.';
    }

    config = config || {
        // bitsPerSecond: 256 * 8 * 1024,
        mimeType: 'video/webm'
    };

    if (config.type === 'audio') {
        if (getTracks(mediaStream, 'video').length && getTracks(mediaStream, 'audio').length) {
            var stream;
            if (!!navigator.mozGetUserMedia) {
                stream = new MediaStream();
                stream.addTrack(getTracks(mediaStream, 'audio')[0]);
            } else {
                // webkitMediaStream
                stream = new MediaStream(getTracks(mediaStream, 'audio'));
            }
            mediaStream = stream;
        }

        if (!config.mimeType || config.mimeType.toString().toLowerCase().indexOf('audio') === -1) {
            config.mimeType = isChrome ? 'audio/webm' : 'audio/ogg';
        }

        if (config.mimeType && config.mimeType.toString().toLowerCase() !== 'audio/ogg' && !!navigator.mozGetUserMedia) {
            // forcing better codecs on Firefox (via #166)
            config.mimeType = 'audio/ogg';
        }
    }

    var arrayOfBlobs = [];

    /**
     * This method returns array of blobs. Use only with "timeSlice". Its useful to preview recording anytime, without using the "stop" method.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var arrayOfBlobs = recorder.getArrayOfBlobs();
     * @returns {Array} Returns array of recorded blobs.
     */
    this.getArrayOfBlobs = function() {
        return arrayOfBlobs;
    };

    /**
     * This method records MediaStream.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.record();
     */
    this.record = function() {
        // set defaults
        self.blob = null;
        self.clearRecordedData();
        self.timestamps = [];
        allStates = [];
        arrayOfBlobs = [];

        var recorderHints = config;

        if (!config.disableLogs) {
            console.log('Passing following config over MediaRecorder API.', recorderHints);
        }

        if (mediaRecorder) {
            // mandatory to make sure Firefox doesn't fails to record streams 3-4 times without reloading the page.
            mediaRecorder = null;
        }

        if (isChrome && !isMediaRecorderCompatible()) {
            // to support video-only recording on stable
            recorderHints = 'video/vp8';
        }

        if (typeof MediaRecorder.isTypeSupported === 'function' && recorderHints.mimeType) {
            if (!MediaRecorder.isTypeSupported(recorderHints.mimeType)) {
                if (!config.disableLogs) {
                    console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
                }

                recorderHints.mimeType = config.type === 'audio' ? 'audio/webm' : 'video/webm';
            }
        }

        // using MediaRecorder API here
        try {
            mediaRecorder = new MediaRecorder(mediaStream, recorderHints);

            // reset
            config.mimeType = recorderHints.mimeType;
        } catch (e) {
            // chrome-based fallback
            mediaRecorder = new MediaRecorder(mediaStream);
        }

        // old hack?
        if (recorderHints.mimeType && !MediaRecorder.isTypeSupported && 'canRecordMimeType' in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === false) {
            if (!config.disableLogs) {
                console.warn('MediaRecorder API seems unable to record mimeType:', recorderHints.mimeType);
            }
        }

        // Dispatching OnDataAvailable Handler
        mediaRecorder.ondataavailable = function(e) {
            if (e.data) {
                allStates.push('ondataavailable: ' + bytesToSize(e.data.size));
            }

            if (typeof config.timeSlice === 'number') {
                if (e.data && e.data.size && e.data.size > 100) {
                    arrayOfBlobs.push(e.data);
                    updateTimeStamp();

                    if (typeof config.ondataavailable === 'function') {
                        // intervals based blobs
                        var blob = config.getNativeBlob ? e.data : new Blob([e.data], {
                            type: getMimeType(recorderHints)
                        });
                        config.ondataavailable(blob);
                    }
                }
                return;
            }

            if (!e.data || !e.data.size || e.data.size < 100 || self.blob) {
                // make sure that stopRecording always getting fired
                // even if there is invalid data
                if (self.recordingCallback) {
                    self.recordingCallback(new Blob([], {
                        type: getMimeType(recorderHints)
                    }));
                    self.recordingCallback = null;
                }
                return;
            }

            self.blob = config.getNativeBlob ? e.data : new Blob([e.data], {
                type: getMimeType(recorderHints)
            });

            if (self.recordingCallback) {
                self.recordingCallback(self.blob);
                self.recordingCallback = null;
            }
        };

        mediaRecorder.onstart = function() {
            allStates.push('started');
        };

        mediaRecorder.onpause = function() {
            allStates.push('paused');
        };

        mediaRecorder.onresume = function() {
            allStates.push('resumed');
        };

        mediaRecorder.onstop = function() {
            allStates.push('stopped');
        };

        mediaRecorder.onerror = function(error) {
            if (!error) {
                return;
            }

            if (!error.name) {
                error.name = 'UnknownError';
            }

            allStates.push('error: ' + error);

            if (!config.disableLogs) {
                // via: https://w3c.github.io/mediacapture-record/MediaRecorder.html#exception-summary
                if (error.name.toString().toLowerCase().indexOf('invalidstate') !== -1) {
                    console.error('The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.', error);
                } else if (error.name.toString().toLowerCase().indexOf('notsupported') !== -1) {
                    console.error('MIME type (', recorderHints.mimeType, ') is not supported.', error);
                } else if (error.name.toString().toLowerCase().indexOf('security') !== -1) {
                    console.error('MediaRecorder security error', error);
                }

                // older code below
                else if (error.name === 'OutOfMemory') {
                    console.error('The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'IllegalStreamModification') {
                    console.error('A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'OtherRecordingError') {
                    console.error('Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.', error);
                } else if (error.name === 'GenericError') {
                    console.error('The UA cannot provide the codec or recording option that has been requested.', error);
                } else {
                    console.error('MediaRecorder Error', error);
                }
            }

            (function(looper) {
                if (!self.manuallyStopped && mediaRecorder && mediaRecorder.state === 'inactive') {
                    delete config.timeslice;

                    // 10 minutes, enough?
                    mediaRecorder.start(10 * 60 * 1000);
                    return;
                }

                setTimeout(looper, 1000);
            })();

            if (mediaRecorder.state !== 'inactive' && mediaRecorder.state !== 'stopped') {
                mediaRecorder.stop();
            }
        };

        if (typeof config.timeSlice === 'number') {
            updateTimeStamp();
            mediaRecorder.start(config.timeSlice);
        } else {
            // default is 60 minutes; enough?
            // use config => {timeSlice: 1000} otherwise

            mediaRecorder.start(3.6e+6);
        }

        if (config.initCallback) {
            config.initCallback(); // old code
        }
    };

    /**
     * @property {Array} timestamps - Array of time stamps
     * @memberof MediaStreamRecorder
     * @example
     * console.log(recorder.timestamps);
     */
    this.timestamps = [];

    function updateTimeStamp() {
        self.timestamps.push(new Date().getTime());

        if (typeof config.onTimeStamp === 'function') {
            config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps);
        }
    }

    function getMimeType(secondObject) {
        if (mediaRecorder && mediaRecorder.mimeType) {
            return mediaRecorder.mimeType;
        }

        return secondObject.mimeType || 'video/webm';
    }

    /**
     * This method stops recording MediaStream.
     * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.stop(function(blob) {
     *     video.src = URL.createObjectURL(blob);
     * });
     */
    this.stop = function(callback) {
        callback = callback || function() {};

        self.manuallyStopped = true; // used inside the mediaRecorder.onerror

        if (!mediaRecorder) {
            return;
        }

        this.recordingCallback = callback;

        if (mediaRecorder.state === 'recording') {
            mediaRecorder.stop();
        }

        if (typeof config.timeSlice === 'number') {
            setTimeout(function() {
                self.blob = new Blob(arrayOfBlobs, {
                    type: getMimeType(config)
                });

                self.recordingCallback(self.blob);
            }, 100);
        }
    };

    /**
     * This method pauses the recording process.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.pause();
     */
    this.pause = function() {
        if (!mediaRecorder) {
            return;
        }

        if (mediaRecorder.state === 'recording') {
            mediaRecorder.pause();
        }
    };

    /**
     * This method resumes the recording process.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.resume();
     */
    this.resume = function() {
        if (!mediaRecorder) {
            return;
        }

        if (mediaRecorder.state === 'paused') {
            mediaRecorder.resume();
        }
    };

    /**
     * This method resets currently recorded data.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * recorder.clearRecordedData();
     */
    this.clearRecordedData = function() {
        if (mediaRecorder && mediaRecorder.state === 'recording') {
            self.stop(clearRecordedDataCB);
        }

        clearRecordedDataCB();
    };

    function clearRecordedDataCB() {
        arrayOfBlobs = [];
        mediaRecorder = null;
        self.timestamps = [];
    }

    // Reference to "MediaRecorder" object
    var mediaRecorder;

    /**
     * Access to native MediaRecorder API
     * @method
     * @memberof MediaStreamRecorder
     * @instance
     * @example
     * var internal = recorder.getInternalRecorder();
     * internal.ondataavailable = function() {}; // override
     * internal.stream, internal.onpause, internal.onstop, etc.
     * @returns {Object} Returns internal recording object.
     */
    this.getInternalRecorder = function() {
        return mediaRecorder;
    };

    function isMediaStreamActive() {
        if ('active' in mediaStream) {
            if (!mediaStream.active) {
                return false;
            }
        } else if ('ended' in mediaStream) { // old hack
            if (mediaStream.ended) {
                return false;
            }
        }
        return true;
    }

    /**
     * @property {Blob} blob - Recorded data as "Blob" object.
     * @memberof MediaStreamRecorder
     * @example
     * recorder.stop(function() {
     *     var blob = recorder.blob;
     * });
     */
    this.blob = null;

    /**
     * Get MediaRecorder readonly state.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var state = recorder.getState();
     * @returns {String} Returns recording state.
     */
    this.getState = function() {
        if (!mediaRecorder) {
            return 'inactive';
        }

        return mediaRecorder.state || 'inactive';
    };

    // list of all recording states
    var allStates = [];

    /**
     * Get MediaRecorder all recording states.
     * @method
     * @memberof MediaStreamRecorder
     * @example
     * var state = recorder.getAllStates();
     * @returns {Array} Returns all recording states
     */
    this.getAllStates = function() {
        return allStates;
    };

    // if any Track within the MediaStream is muted or not enabled at any time,
    // the browser will only record black frames
    // or silence since that is the content produced by the Track
    // so we need to stopRecording as soon as any single track ends.
    if (typeof config.checkForInactiveTracks === 'undefined') {
        config.checkForInactiveTracks = false; // disable to minimize CPU usage
    }

    var self = this;

    // this method checks if media stream is stopped
    // or if any track is ended.
    (function looper() {
        if (!mediaRecorder || config.checkForInactiveTracks === false) {
            return;
        }

        if (isMediaStreamActive() === false) {
            if (!config.disableLogs) {
                console.log('MediaStream seems stopped.');
            }
            self.stop();
            return;
        }

        setTimeout(looper, 1000); // check every second
    })();

    // for debugging
    this.name = 'MediaStreamRecorder';
    this.toString = function() {
        return this.name;
    };
}

if (typeof RecordRTC !== 'undefined') {
    RecordRTC.MediaStreamRecorder = MediaStreamRecorder;
}
<|endoftext|>


<commit_before>/* -----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/

const plib = require('path');
const fs = require('fs-extra');
const Handlebars = require('handlebars');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge').default;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;
const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base');
const { ModuleFederationPlugin } = webpack.container;

const Build = require('@jupyterlab/builder').Build;
const WPPlugin = require('@jupyterlab/builder').WPPlugin;
const package_data = require('./package.json');

// Handle the extensions.
const jlab = package_data.jupyterlab;
const { extensions, mimeExtensions, externalExtensions } = jlab;

// Add external extensions to the extensions/mimeExtensions data as
// appropriate
for (const key in externalExtensions) {
  const {
    jupyterlab: { extension, mimeExtension }
  } = require(`${key}/package.json`);
  if (extension !== undefined) {
    extensions[key] = extension === true ? '' : extension;
  }
  if (mimeExtension !== undefined) {
    mimeExtensions[key] = mimeExtension === true ? '' : mimeExtension;
  }
}

// Deduplicated list of extension package names.
const extensionPackages = [
  ...new Set([...Object.keys(extensions), ...Object.keys(mimeExtensions)])
];

// Ensure a clear build directory.
const buildDir = plib.resolve(jlab.buildDir);
if (fs.existsSync(buildDir)) {
  fs.removeSync(buildDir);
}
fs.ensureDirSync(buildDir);

const outputDir = plib.resolve(jlab.outputDir);

// Build the assets
const extraConfig = Build.ensureAssets({
  // Deduplicate the extension package names
  packageNames: extensionPackages,
  output: outputDir
});

// Create the entry point and other assets in build directory.
const source = fs.readFileSync('index.js').toString();
const template = Handlebars.compile(source);
const extData = {
  jupyterlab_extensions: extensions,
  jupyterlab_mime_extensions: mimeExtensions
};
fs.writeFileSync(plib.join(buildDir, 'index.out.js'), template(extData));

// Create the bootstrap file that loads federated extensions and calls the
// initialization logic in index.out.js
const entryPoint = plib.join(buildDir, 'bootstrap.js');
fs.copySync('./bootstrap.js', entryPoint);

fs.copySync('./package.json', plib.join(buildDir, 'package.json'));
if (outputDir !== buildDir) {
  fs.copySync(
    plib.join(outputDir, 'imports.css'),
    plib.join(buildDir, 'imports.css')
  );
}

// Set up variables for the watch mode ignore plugins
const watched = {};
const ignoreCache = Object.create(null);
let watchNodeModules = false;
Object.keys(jlab.linkedPackages).forEach(function (name) {
  if (name in watched) {
    return;
  }
  const localPkgPath = require.resolve(plib.join(name, 'package.json'));
  watched[name] = plib.dirname(localPkgPath);
  if (localPkgPath.indexOf('node_modules') !== -1) {
    watchNodeModules = true;
  }
});

// Set up source-map-loader to look in watched lib dirs
const sourceMapRes = Object.values(watched).reduce((res, name) => {
  res.push(new RegExp(name + '/lib'));
  return res;
}, []);

/**
 * Sync a local path to a linked package path if they are files and differ.
 * This is used by `jupyter lab --watch` to synchronize linked packages
 * and has no effect in `jupyter lab --dev-mode --watch`.
 */
function maybeSync(localPath, name, rest) {
  const stats = fs.statSync(localPath);
  if (!stats.isFile(localPath)) {
    return;
  }
  const source = fs.realpathSync(plib.join(jlab.linkedPackages[name], rest));
  if (source === fs.realpathSync(localPath)) {
    return;
  }
  fs.watchFile(source, { interval: 500 }, function (curr) {
    if (!curr || curr.nlink === 0) {
      return;
    }
    try {
      fs.copySync(source, localPath);
    } catch (err) {
      console.error(err);
    }
  });
}

/**
 * A filter function set up to exclude all files that are not
 * in a package contained by the Jupyterlab repo. Used to ignore
 * files during a `--watch` build.
 */
function ignored(path) {
  path = plib.resolve(path);
  if (path in ignoreCache) {
    // Bail if already found.
    return ignoreCache[path];
  }

  // Limit the watched files to those in our local linked package dirs.
  let ignore = true;
  Object.keys(watched).some(name => {
    const rootPath = watched[name];
    const contained = path.indexOf(rootPath + plib.sep) !== -1;
    if (path !== rootPath && !contained) {
      return false;
    }
    const rest = path.slice(rootPath.length);
    if (rest.indexOf('node_modules') === -1) {
      ignore = false;
      maybeSync(path, name, rest);
    }
    return true;
  });
  ignoreCache[path] = ignore;
  return ignore;
}

// Set up module federation sharing config
const shared = {};

// Make sure any resolutions are shared
for (let [key, requiredVersion] of Object.entries(package_data.resolutions)) {
  // eager so that built-in extensions can be bundled together into just a few
  // js files to load
  shared[key] = { requiredVersion, eager: true };
}

// Add any extension packages that are not in resolutions (i.e., installed from npm)
for (let pkg of extensionPackages) {
  if (shared[pkg] === undefined) {
    shared[pkg] = {
      requiredVersion: require(`${pkg}/package.json`).version,
      eager: true
    };
  }
}

// Add dependencies and sharedPackage config from extension packages if they
// are not already in the shared config. This means that if there is a
// conflict, the resolutions package version is the one that is shared.
extraShared = [];
for (let pkg of extensionPackages) {
  let pkgShared = {};
  let {
    dependencies = {},
    jupyterlab: { sharedPackages = {} } = {}
  } = require(`${pkg}/package.json`);
  for (let [dep, requiredVersion] of Object.entries(dependencies)) {
    if (!shared[dep]) {
      pkgShared[dep] = { requiredVersion, eager: true };
    }
  }

  // Overwrite automatic dependency sharing with custom sharing config
  for (let [pkg, config] of Object.entries(sharedPackages)) {
    if (config === false) {
      delete pkgShared[pkg];
    } else {
      if ('bundled' in config) {
        config.import = config.bundled;
        delete config.bundled;
      }
      pkgShared[pkg] = config;
    }
  }
  extraShared.push(pkgShared);
}

// Now merge the extra shared config
const mergedShare = {};
for (let sharedConfig of extraShared) {
  for (let [pkg, config] of Object.entries(sharedConfig)) {
    // Do not override the basic share config from resolutions
    if (shared[pkg]) {
      continue;
    }

    // Add if we haven't seen the config before
    if (!mergedShare[pkg]) {
      mergedShare[pkg] = config;
      continue;
    }

    // Choose between the existing config and this new config. We do not try
    // to merge configs, which may yield a config no one wants
    let oldConfig = mergedShare[pkg];

    // if the old one has import: false, use the new one
    if (oldConfig.import === false) {
      mergedShare[pkg] = config;
    }
  }
}

Object.assign(shared, mergedShare);

// Transform any file:// requiredVersion to the version number from the
// imported package. This assumes (for simplicity) that the version we get
// importing was installed from the file.
for (let [key, { requiredVersion }] of Object.entries(shared)) {
  if (requiredVersion && requiredVersion.startsWith('file:')) {
    shared[key].requiredVersion = require(`${key}/package.json`).version;
  }
}

// Add singleton package information
for (let key of jlab.singletonPackages) {
  shared[key].singleton = true;
}

const plugins = [
  new WPPlugin.NowatchDuplicatePackageCheckerPlugin({
    verbose: true,
    exclude(instance) {
      // ignore known duplicates
      return ['domelementtype', 'hash-base', 'inherits'].includes(
        instance.name
      );
    }
  }),
  new HtmlWebpackPlugin({
    chunksSortMode: 'none',
    template: plib.join(__dirname, 'templates', 'template.html'),
    title: jlab.name || 'JupyterLab'
  }),
  // custom plugin for ignoring files during a `--watch` build
  new WPPlugin.FilterWatchIgnorePlugin(ignored),
  // custom plugin that copies the assets to the static directory
  new WPPlugin.FrontEndPlugin(buildDir, jlab.staticDir),
  new ModuleFederationPlugin({
    library: {
      type: 'var',
      name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION']
    },
    name: 'CORE_FEDERATION',
    shared
  })
];

if (process.argv.includes('--analyze')) {
  plugins.push(new BundleAnalyzerPlugin());
}

module.exports = [
  merge(baseConfig, {
    mode: 'development',
    entry: {
      main: ['./publicpath', 'whatwg-fetch', entryPoint]
    },
    output: {
      path: plib.resolve(buildDir),
      publicPath: '{{page_config.fullStaticUrl}}/',
      filename: '[name].[contenthash].js'
    },
    optimization: {
      splitChunks: {
        chunks: 'all'
      }
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: sourceMapRes,
          use: ['source-map-loader'],
          enforce: 'pre'
        }
      ]
    },
    devtool: 'inline-source-map',
    externals: ['node-fetch', 'ws'],
    plugins
  })
].concat(extraConfig);

// Needed to watch changes in linked extensions in node_modules
// (jupyter lab --watch)
// See https://github.com/webpack/webpack/issues/11612
if (watchNodeModules) {
  module.exports[0].snapshot = { managedPaths: [] };
}

const logPath = plib.join(buildDir, 'build_log.json');
fs.writeFileSync(logPath, JSON.stringify(module.exports, null, '  '));
<commit_msg>Only make core packages or dependencies of core packages eagerly shared.<commit_after>/* -----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/

const plib = require('path');
const fs = require('fs-extra');
const Handlebars = require('handlebars');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge').default;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;
const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base');
const { ModuleFederationPlugin } = webpack.container;

const Build = require('@jupyterlab/builder').Build;
const WPPlugin = require('@jupyterlab/builder').WPPlugin;
const package_data = require('./package.json');

// Handle the extensions.
const jlab = package_data.jupyterlab;
const { extensions, mimeExtensions, externalExtensions } = jlab;

// Add external extensions to the extensions/mimeExtensions data as
// appropriate
for (const key in externalExtensions) {
  const {
    jupyterlab: { extension, mimeExtension }
  } = require(`${key}/package.json`);
  if (extension !== undefined) {
    extensions[key] = extension === true ? '' : extension;
  }
  if (mimeExtension !== undefined) {
    mimeExtensions[key] = mimeExtension === true ? '' : mimeExtension;
  }
}

// Deduplicated list of extension package names.
const extensionPackages = [
  ...new Set([...Object.keys(extensions), ...Object.keys(mimeExtensions)])
];

// Ensure a clear build directory.
const buildDir = plib.resolve(jlab.buildDir);
if (fs.existsSync(buildDir)) {
  fs.removeSync(buildDir);
}
fs.ensureDirSync(buildDir);

const outputDir = plib.resolve(jlab.outputDir);

// Build the assets
const extraConfig = Build.ensureAssets({
  // Deduplicate the extension package names
  packageNames: extensionPackages,
  output: outputDir
});

// Create the entry point and other assets in build directory.
const source = fs.readFileSync('index.js').toString();
const template = Handlebars.compile(source);
const extData = {
  jupyterlab_extensions: extensions,
  jupyterlab_mime_extensions: mimeExtensions
};
fs.writeFileSync(plib.join(buildDir, 'index.out.js'), template(extData));

// Create the bootstrap file that loads federated extensions and calls the
// initialization logic in index.out.js
const entryPoint = plib.join(buildDir, 'bootstrap.js');
fs.copySync('./bootstrap.js', entryPoint);

fs.copySync('./package.json', plib.join(buildDir, 'package.json'));
if (outputDir !== buildDir) {
  fs.copySync(
    plib.join(outputDir, 'imports.css'),
    plib.join(buildDir, 'imports.css')
  );
}

// Set up variables for the watch mode ignore plugins
const watched = {};
const ignoreCache = Object.create(null);
let watchNodeModules = false;
Object.keys(jlab.linkedPackages).forEach(function (name) {
  if (name in watched) {
    return;
  }
  const localPkgPath = require.resolve(plib.join(name, 'package.json'));
  watched[name] = plib.dirname(localPkgPath);
  if (localPkgPath.indexOf('node_modules') !== -1) {
    watchNodeModules = true;
  }
});

// Set up source-map-loader to look in watched lib dirs
const sourceMapRes = Object.values(watched).reduce((res, name) => {
  res.push(new RegExp(name + '/lib'));
  return res;
}, []);

/**
 * Sync a local path to a linked package path if they are files and differ.
 * This is used by `jupyter lab --watch` to synchronize linked packages
 * and has no effect in `jupyter lab --dev-mode --watch`.
 */
function maybeSync(localPath, name, rest) {
  const stats = fs.statSync(localPath);
  if (!stats.isFile(localPath)) {
    return;
  }
  const source = fs.realpathSync(plib.join(jlab.linkedPackages[name], rest));
  if (source === fs.realpathSync(localPath)) {
    return;
  }
  fs.watchFile(source, { interval: 500 }, function (curr) {
    if (!curr || curr.nlink === 0) {
      return;
    }
    try {
      fs.copySync(source, localPath);
    } catch (err) {
      console.error(err);
    }
  });
}

/**
 * A filter function set up to exclude all files that are not
 * in a package contained by the Jupyterlab repo. Used to ignore
 * files during a `--watch` build.
 */
function ignored(path) {
  path = plib.resolve(path);
  if (path in ignoreCache) {
    // Bail if already found.
    return ignoreCache[path];
  }

  // Limit the watched files to those in our local linked package dirs.
  let ignore = true;
  Object.keys(watched).some(name => {
    const rootPath = watched[name];
    const contained = path.indexOf(rootPath + plib.sep) !== -1;
    if (path !== rootPath && !contained) {
      return false;
    }
    const rest = path.slice(rootPath.length);
    if (rest.indexOf('node_modules') === -1) {
      ignore = false;
      maybeSync(path, name, rest);
    }
    return true;
  });
  ignoreCache[path] = ignore;
  return ignore;
}

// Set up module federation sharing config
const shared = {};

// eager so that built-in extensions can be bundled together into just a few
// js files to load
const eagerPackages = new Set(Object.keys(package_data.resolutions));
for (let pkg of Object.keys(jlab.linkedPackages)) {
  eagerPackages.delete(pkg);
}

// Make sure any resolutions are shared
for (let [key, requiredVersion] of Object.entries(package_data.resolutions)) {
  shared[key] = { requiredVersion};
  if (eagerPackages.has(key)) {
    shared[key].eager = true;
  }
}

// Add any extension packages that are not in resolutions (i.e., installed from npm)
for (let pkg of extensionPackages) {
  if (shared[pkg] === undefined) {
    shared[pkg] = {
      requiredVersion: require(`${pkg}/package.json`).version
    };
    if (eagerPackages.has(pkg)) {
      shared[pkg].eager = true;
    }
  }
}

// Add dependencies and sharedPackage config from extension packages if they
// are not already in the shared config. This means that if there is a
// conflict, the resolutions package version is the one that is shared.
extraShared = [];
// dependencies of eager shared packages must also be eager.
for (let pkg of extensionPackages) {
  let eager = eagerPackages.has(pkg);
  let pkgShared = {};
  let {
    dependencies = {},
    jupyterlab: { sharedPackages = {} } = {}
  } = require(`${pkg}/package.json`);
  for (let [dep, requiredVersion] of Object.entries(dependencies)) {
    if (!shared[dep]) {
      pkgShared[dep] = { requiredVersion };
      if (eager) {
        pkgShared[dep].eager = true;
      }
    } else if (eager) {
      // Even if we already have this dependency, make sure it is eager if it
      // is the dependency of an eager package.
      shared[dep].eager = true;
    }
  }

  // Overwrite automatic dependency sharing with custom sharing config
  for (let [pkg, config] of Object.entries(sharedPackages)) {
    if (config === false) {
      delete pkgShared[pkg];
    } else {
      if ('bundled' in config) {
        config.import = config.bundled;
        delete config.bundled;
      }
      pkgShared[pkg] = config;
    }
  }
  extraShared.push(pkgShared);
}

// Now merge the extra shared config
const mergedShare = {};
for (let sharedConfig of extraShared) {
  for (let [pkg, config] of Object.entries(sharedConfig)) {
    // Do not override the basic share config from resolutions
    if (shared[pkg]) {
      continue;
    }

    // Add if we haven't seen the config before
    if (!mergedShare[pkg]) {
      mergedShare[pkg] = config;
      continue;
    }

    // Choose between the existing config and this new config. We do not try
    // to merge configs, which may yield a config no one wants
    let oldConfig = mergedShare[pkg];

    // if the old one has import: false, use the new one
    if (oldConfig.import === false) {
      mergedShare[pkg] = config;
    }
  }
}

Object.assign(shared, mergedShare);

// Transform any file:// requiredVersion to the version number from the
// imported package. This assumes (for simplicity) that the version we get
// importing was installed from the file.
for (let [key, { requiredVersion }] of Object.entries(shared)) {
  if (requiredVersion && requiredVersion.startsWith('file:')) {
    shared[key].requiredVersion = require(`${key}/package.json`).version;
  }
}

// Add singleton package information
for (let key of jlab.singletonPackages) {
  shared[key].singleton = true;
}

const plugins = [
  new WPPlugin.NowatchDuplicatePackageCheckerPlugin({
    verbose: true,
    exclude(instance) {
      // ignore known duplicates
      return ['domelementtype', 'hash-base', 'inherits'].includes(
        instance.name
      );
    }
  }),
  new HtmlWebpackPlugin({
    chunksSortMode: 'none',
    template: plib.join(__dirname, 'templates', 'template.html'),
    title: jlab.name || 'JupyterLab'
  }),
  // custom plugin for ignoring files during a `--watch` build
  new WPPlugin.FilterWatchIgnorePlugin(ignored),
  // custom plugin that copies the assets to the static directory
  new WPPlugin.FrontEndPlugin(buildDir, jlab.staticDir),
  new ModuleFederationPlugin({
    library: {
      type: 'var',
      name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION']
    },
    name: 'CORE_FEDERATION',
    shared
  })
];

if (process.argv.includes('--analyze')) {
  plugins.push(new BundleAnalyzerPlugin());
}

module.exports = [
  merge(baseConfig, {
    mode: 'development',
    entry: {
      main: ['./publicpath', 'whatwg-fetch', entryPoint]
    },
    output: {
      path: plib.resolve(buildDir),
      publicPath: '{{page_config.fullStaticUrl}}/',
      filename: '[name].[contenthash].js'
    },
    optimization: {
      splitChunks: {
        chunks: 'all'
      }
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: sourceMapRes,
          use: ['source-map-loader'],
          enforce: 'pre'
        }
      ]
    },
    devtool: 'inline-source-map',
    externals: ['node-fetch', 'ws'],
    plugins
  })
].concat(extraConfig);

// Needed to watch changes in linked extensions in node_modules
// (jupyter lab --watch)
// See https://github.com/webpack/webpack/issues/11612
if (watchNodeModules) {
  module.exports[0].snapshot = { managedPaths: [] };
}

const logPath = plib.join(buildDir, 'build_log.json');
fs.writeFileSync(logPath, JSON.stringify(module.exports, null, '  '));
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var portfinder = require('portfinder');
var spawn = require('child_process').spawn;
var request = require('request');
var jsdom = require('jsdom');
var semver = require('semver');
var colors = require('colors/safe');

var previewCode;

var storybookCheck = function() {
  var pjson = require(process.cwd() + '/package.json');
  var storybookVersion = pjson.dependencies['@kadira/storybook'] || pjson.devDependencies['@kadira/storybook'];
  // check if storybook exists
  if (!storybookVersion) {
    throw new Error('Storybook module not found in package.json');
  }
  // check storybook version
  if ((!semver.satisfies('2.17.0', storybookVersion) && !semver.ltr('2.17.0', storybookVersion)) || semver.satisfies('3.0.0', storybookVersion)) {
    throw new Error('Storybook version must be >= 2.17.0 and < 3.x');
  }
};

exports.server = function(config, options, callback) {
  // check storybook module
  try {
    storybookCheck();
  } catch(ex) {
    return callback(ex);
  }
  if (!config || !config.storybookConfigDir) {
    return callback(new Error('Error: \'storybookConfigDir\' not found in config file.'));
  }
  // find free port
  portfinder.getPort(function (err, port) {
    if (err) return callback(err);
    // inject code into config file to get storybook and store in global variable
    // FIXME: this is hacky; is there a better way of doing this?
    var configPath = path.resolve(process.cwd(), config.storybookConfigDir, 'config.js');
    if (!fs.existsSync(configPath)) {
      return callback(new Error('Storybook config file not found: ' + configPath));
    }
    var configBody;
    var code = '\nif (typeof window === \'object\') window.__screener_storybook__ = require(\'@kadira/storybook\').getStorybook();';
    try {
      // save contents of config file
      configBody = fs.readFileSync(configPath, 'utf8');
      if (configBody.indexOf(code) === -1) {
        // append code to config file
        fs.appendFileSync(configPath, code, 'utf8');
      } else {
        // code is already in config file
        configBody = null;
      }
    } catch(ex) {
      return callback(ex);
    }
    // start Storybook dev server
    var bin = path.resolve(process.cwd(), 'node_modules/.bin/start-storybook');
    var args = ['--port', port, '--config-dir', config.storybookConfigDir];
    if (config.storybookStaticDir) {
      args.push('--static-dir');
      args.push(config.storybookStaticDir);
    }
    console.log('\nStarting Storybook server...');
    console.log('>', 'start-storybook', args.join(' '), '\n');
    var serverProcess = spawn(bin, args, {detached: true});
    if (options && (options.debug || options.serverOnly)) {
      serverProcess.stdout.on('data', function(data) { console.log(data.toString('utf8').trim()); });
      serverProcess.stderr.on('data', function(data) { console.error(data.toString('utf8').trim()); });
    }
    // clean-up all child processes when this process is terminated
    process.on('exit', function() {
      process.kill(-serverProcess.pid);
    });
    process.on('SIGINT', function () {
      process.exit();
    });
    process.on('uncaughtException', function() {
      process.exit(1);
    });
    // wait for storybook server to be ready
    setTimeout(function() {
      request.get('http://localhost:' + port + '/static/preview.bundle.js', function(err, response, body) {
        if (err) return callback(err);
        if (response.statusCode != 200 || !body) return callback(new Error('Error fetching preview bundle from storybook server'));
        previewCode = body;
        try {
          // reset config file to original code
          if (configBody) fs.writeFileSync(configPath, configBody, 'utf8');
        } catch(ex) {
          return callback(ex);
        }
        callback(null, port);
      });
    }, 3*1000);
  });
};

exports.get = function(options, callback) {
  var setupCode = 'window.matchMedia = window.matchMedia || (() => { return { matches: false, addListener: () => {}, removeListener: () => {}, }; });';
  var jsDomConfig = {
    html: '',
    src: [setupCode + previewCode],
    done: function (err, window) {
      if (err) return callback(err);
      if (!window || !window.__screener_storybook__) {
        console.error(colors.red('Error getting Storybook object'));
        if (options && options.debug) {
          console.error(colors.red('Please send debug output to support@screener.io'));
        } else {
          console.error(colors.red('Please re-run with --debug flag, and send debug output to support@screener.io'));
        }
        return callback(new Error('Storybook object not found'));
      }
      callback(null, window.__screener_storybook__);
    }
  };
  if (options && options.debug) {
    jsDomConfig.virtualConsole = jsdom.createVirtualConsole().sendTo(console);
  }
  // parse preview bundle js code and retrieve window object
  jsdom.env(jsDomConfig);
};
<commit_msg>check for and handle storybook v2 or v3 package<commit_after>var path = require('path');
var fs = require('fs');
var portfinder = require('portfinder');
var spawn = require('child_process').spawn;
var request = require('request');
var jsdom = require('jsdom');
var semver = require('semver');
var colors = require('colors/safe');

var storybookVersion;
var previewCode;

var storybookCheck = function() {
  var pjson = require(process.cwd() + '/package.json');
  var storybookRange = pjson.dependencies['@storybook/react'] || pjson.devDependencies['@storybook/react'];
  if (storybookRange) {
    storybookVersion = 3;
  } else {
    storybookRange = pjson.dependencies['@kadira/storybook'] || pjson.devDependencies['@kadira/storybook'];
    if (storybookRange) {
      storybookVersion = 2;
    }
  }
  // check if storybook exists
  if (!storybookVersion) {
    throw new Error('Storybook module not found in package.json');
  }
  // check storybook version range
  if ((!semver.satisfies('2.17.0', storybookRange) && !semver.ltr('2.17.0', storybookRange)) || !semver.gtr('4.0.0', storybookRange)) {
    throw new Error('Storybook version must be >= 2.17.0 and < 4.x');
  }
};

exports.server = function(config, options, callback) {
  // check storybook module
  try {
    storybookCheck();
  } catch(ex) {
    return callback(ex);
  }
  if (!config || !config.storybookConfigDir) {
    return callback(new Error('Error: \'storybookConfigDir\' not found in config file.'));
  }
  // find free port
  portfinder.getPort(function (err, port) {
    if (err) return callback(err);
    // inject code into config file to get storybook and store in global variable
    // FIXME: this is hacky; is there a better way of doing this?
    var configPath = path.resolve(process.cwd(), config.storybookConfigDir, 'config.js');
    if (!fs.existsSync(configPath)) {
      return callback(new Error('Storybook config file not found: ' + configPath));
    }
    var configBody;
    var packageName = '@storybook/react';
    if (storybookVersion === 2) {
      packageName = '@kadira/storybook';
    }
    var code = '\nif (typeof window === \'object\') window.__screener_storybook__ = require(\'' + packageName + '\').getStorybook();';
    try {
      // save contents of config file
      configBody = fs.readFileSync(configPath, 'utf8');
      if (configBody.indexOf(code) === -1) {
        // append code to config file
        fs.appendFileSync(configPath, code, 'utf8');
      } else {
        // code is already in config file
        configBody = null;
      }
    } catch(ex) {
      return callback(ex);
    }
    // start Storybook dev server
    var bin = path.resolve(process.cwd(), 'node_modules/.bin/start-storybook');
    var args = ['--port', port, '--config-dir', config.storybookConfigDir];
    if (config.storybookStaticDir) {
      args.push('--static-dir');
      args.push(config.storybookStaticDir);
    }
    console.log('\nStarting Storybook server...');
    console.log('>', 'start-storybook', args.join(' '), '\n');
    var serverProcess = spawn(bin, args, {detached: true});
    if (options && (options.debug || options.serverOnly)) {
      serverProcess.stdout.on('data', function(data) { console.log(data.toString('utf8').trim()); });
      serverProcess.stderr.on('data', function(data) { console.error(data.toString('utf8').trim()); });
    }
    // clean-up all child processes when this process is terminated
    process.on('exit', function() {
      process.kill(-serverProcess.pid);
    });
    process.on('SIGINT', function () {
      process.exit();
    });
    process.on('uncaughtException', function() {
      process.exit(1);
    });
    // wait for storybook server to be ready
    setTimeout(function() {
      request.get('http://localhost:' + port + '/static/preview.bundle.js', function(err, response, body) {
        if (err) return callback(err);
        if (response.statusCode != 200 || !body) return callback(new Error('Error fetching preview bundle from storybook server'));
        previewCode = body;
        try {
          // reset config file to original code
          if (configBody) fs.writeFileSync(configPath, configBody, 'utf8');
        } catch(ex) {
          return callback(ex);
        }
        callback(null, port);
      });
    }, 3*1000);
  });
};

exports.get = function(options, callback) {
  var setupCode = 'window.matchMedia = window.matchMedia || (() => { return { matches: false, addListener: () => {}, removeListener: () => {}, }; });';
  var jsDomConfig = {
    html: '',
    src: [setupCode + previewCode],
    done: function (err, window) {
      if (err) return callback(err);
      if (!window || !window.__screener_storybook__) {
        console.error(colors.red('Error getting Storybook object'));
        if (options && options.debug) {
          console.error(colors.red('Please send debug output to support@screener.io'));
        } else {
          console.error(colors.red('Please re-run with --debug flag, and send debug output to support@screener.io'));
        }
        return callback(new Error('Storybook object not found'));
      }
      callback(null, window.__screener_storybook__);
    }
  };
  if (options && options.debug) {
    jsDomConfig.virtualConsole = jsdom.createVirtualConsole().sendTo(console);
  }
  // parse preview bundle js code and retrieve window object
  jsdom.env(jsDomConfig);
};
<|endoftext|>


<commit_before>'use strict';

var PROJECT_DIR = fis.project.getProjectPath();

var path = require('path');
var through = require('through');
var minify = require('html-minifier').minify;

function hasStringifiableExtension (filename, extensions) {
    var fileExtName = path.extname(filename).toLowerCase();
    return extensions.indexOf(fileExtName) >= 0;
}

function content2string (content) {
  return 'module.exports = ' + JSON.stringify(content) + ';\n';
}

module.exports = function (extensions, htmlMinifierOptions, fisRet) {
    if (!extensions) {
        extensions = [];
    }
    if (!fisRet) {
        fisRet = {};
    }

    return function (inputFileRealPath) {
        if (!hasStringifiableExtension(inputFileRealPath, extensions)) {
            return through();
        }

        var onwrite = function () {};
        var onend = function () {
            // if (fis.util.isFile(inputFileRealPath)) {
            var retSrc = '/' + path.relative(PROJECT_DIR, inputFileRealPath);
            var content = fisRet.src[retSrc].getContent();
            this.queue(content2string(minify(content, htmlMinifierOptions || {})));
            this.queue(null);
        };
        return through(onwrite, onend);
    };
};
<commit_msg>fix file path in windows<commit_after>'use strict';

var PROJECT_DIR = fis.project.getProjectPath();

var path = require('path');
var through = require('through');
var minify = require('html-minifier').minify;

function hasStringifiableExtension (filename, extensions) {
    var fileExtName = path.extname(filename).toLowerCase();
    return extensions.indexOf(fileExtName) >= 0;
}

function content2string (content) {
  return 'module.exports = ' + JSON.stringify(content) + ';\n';
}

module.exports = function (extensions, htmlMinifierOptions, fisRet) {
    if (!extensions) {
        extensions = [];
    }
    if (!fisRet) {
        fisRet = {};
    }

    return function (inputFileRealPath) {
        if (!hasStringifiableExtension(inputFileRealPath, extensions)) {
            return through();
        }

        var onwrite = function () {};
        var onend = function () {
            var retSrc = '/' + path.relative(PROJECT_DIR, inputFileRealPath).replace(/\\/g, '/');
            var contentFile = fisRet.src[retSrc];
            var content = contentFile ?
                contentFile.getContent() :
                '
file `' + retSrc + '` NOT found!
';
            this.queue(content2string(minify(content, htmlMinifierOptions || {})));
            this.queue(null);
        };
        return through(onwrite, onend);
    };
};
<|endoftext|>


<commit_before>var Hoek = require('hoek');
var Vision = require('vision');

exports.register = function (server, options, next) {

    server.register(Vision, function (err) {

        Hoek.assert(!err, err);

        server.views({
            engines: {
                hbs: require('handlebars')
            },
            relativeTo: [__dirname, '..'].join('/'),
            path: 'templates'
        });

    });

    server.route({
        method: 'GET',
        path: '/example',
        handler: function (request, reply) {

            reply.view('example', {
                title: 'example of handlebars with hapi',
                name: 'Pea-tear-griffon',
            })
        }
    });

    server.route({
        method: 'GET',
        path: '/fb',
        handler: function (request, reply) {

            var fbdata = {};

            // get a copy of the fb data
            server.inject('/api/local/fb', function (res) {

                fbdata.result = res.result;

                fbdata = Hoek.merge(fbdata, {
                    title: 'using fixture data with views',
                });

                require('purdy')(fbdata);

                reply.view('fb', fbdata);
            });
        }
    });

    next();
};

exports.register.attributes = {
    name: 'server-views',
    version: '1.0.0'
};
<commit_msg>refactor(week 3): views * fix nesting * remove debugging * bump plugin version (patch release)<commit_after>var Hoek = require('hoek');
var Vision = require('vision');

exports.register = function (server, options, next) {

    server.register(Vision, function (err) {

        Hoek.assert(!err, err);
    });

    server.views({
        engines: {
            hbs: require('handlebars')
        },
        relativeTo: [__dirname, '..'].join('/'),
        path: 'templates'
    });

    server.route({
        method: 'GET',
        path: '/example',
        handler: function (request, reply) {

            reply.view('example', {
                title: 'example of handlebars with hapi',
                name: 'Pea-tear-griffon',
            })
        }
    });

    server.route({
        method: 'GET',
        path: '/fb',
        handler: function (request, reply) {

            var fbdata = {};

            // get a copy of the fb data
            server.inject('/api/local/fb', function (res) {

                fbdata.result = res.result;

                fbdata = Hoek.merge(fbdata, {
                    title: 'using fixture data with views',
                });

                reply.view('fb', fbdata);
            });
        }
    });

    next();
};

exports.register.attributes = {
    name: 'server-views',
    version: '1.0.1'
};
<|endoftext|>