<commit_before>const createRetry = require('../retry')
const waitFor = require('../utils/waitFor')
const createConsumer = require('../consumer')
const InstrumentationEventEmitter = require('../instrumentation/emitter')
const { events, wrap: wrapEvent, unwrap: unwrapEvent } = require('./instrumentationEvents')
const { LEVELS } = require('../loggers')
const { KafkaJSNonRetriableError } = require('../errors')
const RESOURCE_TYPES = require('../protocol/resourceTypes')

const { CONNECT, DISCONNECT } = events

const { values, keys } = Object
const eventNames = values(events)
const eventKeys = keys(events)
  .map(key => `admin.events.${key}`)
  .join(', ')

const retryOnLeaderNotAvailable = (fn, opts = {}) => {
  const callback = async () => {
    try {
      return await fn()
    } catch (e) {
      if (e.type !== 'LEADER_NOT_AVAILABLE') {
        throw e
      }
      return false
    }
  }

  return waitFor(callback, opts)
}

const isConsumerGroupRunning = description => ['Empty', 'Dead'].includes(description.state)
const findTopicPartitions = async (cluster, topic) => {
  await cluster.addTargetTopic(topic)
  await cluster.refreshMetadataIfNecessary()

  return cluster
    .findTopicPartitionMetadata(topic)
    .map(({ partitionId }) => partitionId)
    .sort()
}

module.exports = ({
  logger: rootLogger,
  instrumentationEmitter: rootInstrumentationEmitter,
  retry = { retries: 5 },
  cluster,
}) => {
  const logger = rootLogger.namespace('Admin')
  const instrumentationEmitter = rootInstrumentationEmitter || new InstrumentationEventEmitter()

  /**
   * @returns {Promise}
   */
  const connect = async () => {
    await cluster.connect()
    instrumentationEmitter.emit(CONNECT)
  }

  /**
   * @return {Promise}
   */
  const disconnect = async () => {
    await cluster.disconnect()
    instrumentationEmitter.emit(DISCONNECT)
  }

  /**
   * @param {array} topics
   * @param {boolean} [validateOnly=false]
   * @param {number} [timeout=5000]
   * @param {boolean} [waitForLeaders=true]
   * @return {Promise}
   */
  const createTopics = async ({ topics, validateOnly, timeout, waitForLeaders = true }) => {
    if (!topics || !Array.isArray(topics)) {
      throw new KafkaJSNonRetriableError(`Invalid topics array ${topics}`)
    }

    if (topics.filter(({ topic }) => typeof topic !== 'string').length > 0) {
      throw new KafkaJSNonRetriableError(
        'Invalid topics array, the topic names have to be a valid string'
      )
    }

    const topicNames = new Set(topics.map(({ topic }) => topic))
    if (topicNames.size < topics.length) {
      throw new KafkaJSNonRetriableError(
        'Invalid topics array, it cannot have multiple entries for the same topic'
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        await broker.createTopics({ topics, validateOnly, timeout })

        if (waitForLeaders) {
          const topicNamesArray = Array.from(topicNames.values())
          await retryOnLeaderNotAvailable(async () => await broker.metadata(topicNamesArray), {
            delay: 100,
            timeoutMessage: 'Timed out while waiting for topic leaders',
          })
        }

        return true
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not create topics', { error: e.message, retryCount, retryTime })
          throw e
        }

        if (e.type === 'TOPIC_ALREADY_EXISTS') {
          return false
        }

        bail(e)
      }
    })
  }

  /**
   * @param {string[]} topics
   * @param {number} [timeout=5000]
   * @return {Promise}
   */
  const deleteTopics = async ({ topics, timeout }) => {
    if (!topics || !Array.isArray(topics)) {
      throw new KafkaJSNonRetriableError(`Invalid topics array ${topics}`)
    }

    if (topics.filter(topic => typeof topic !== 'string').length > 0) {
      throw new KafkaJSNonRetriableError('Invalid topics array, the names must be a valid string')
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        await broker.deleteTopics({ topics, timeout })

        // Remove deleted topics
        for (let topic of topics) {
          cluster.targetTopics.delete(topic)
        }

        await cluster.refreshMetadata()
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not delete topics', { error: e.message, retryCount, retryTime })
          throw e
        }

        if (e.type === 'REQUEST_TIMED_OUT') {
          logger.error(
            'Could not delete topics, check if "delete.topic.enable" is set to "true" (the default value is "false") or increase the timeout',
            {
              error: e.message,
              retryCount,
              retryTime,
            }
          )
        }

        bail(e)
      }
    })
  }

  /**
   * @param {string} topic
   */

  const fetchTopicOffsets = async (topic) => {
    if (!topic || typeof topic !== 'string') {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const topicPartitions = await findTopicPartitions(cluster, topic);
    const partitions = topicPartitions.map(partition => ({ partition }));
    const broker = await cluster.findControllerBroker();
    const offsets = await broker.listOffsets({ topics: [{ topic, partitions }] });

    return offsets.responses
      .find(response => response.topic === topic)
      .partitions.map(({ partition, offset }) => ({ partition, offset }))

  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @return {Promise}
   */
  const fetchOffsets = async ({ groupId, topic }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const partitions = await findTopicPartitions(cluster, topic)
    const coordinator = await cluster.findGroupCoordinator({ groupId })
    const partitionsToFetch = partitions.map(partition => ({ partition }))

    const { responses } = await coordinator.offsetFetch({
      groupId,
      topics: [{ topic, partitions: partitionsToFetch }],
    })

    return responses
      .filter(response => response.topic === topic)
      .map(({ partitions }) => partitions.map(({ partition, offset }) => ({ partition, offset })))
      .pop()
  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @param {boolean} [earliest=false]
   * @return {Promise}
   */
  const resetOffsets = async ({ groupId, topic, earliest = false }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const partitions = await findTopicPartitions(cluster, topic)
    const partitionsToSeek = partitions.map(partition => ({
      partition,
      offset: cluster.defaultOffset({ fromBeginning: earliest }),
    }))

    return setOffsets({ groupId, topic, partitions: partitionsToSeek })
  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @param {Array<SeekEntry>} partitions
   * @return {Promise}
   *
   * @typedef {Object} SeekEntry
   * @property {number} partition
   * @property {string} offset
   */
  const setOffsets = async ({ groupId, topic, partitions }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    if (!partitions || partitions.length === 0) {
      throw new KafkaJSNonRetriableError(`Invalid partitions`)
    }

    const consumer = createConsumer({
      logger: rootLogger.namespace('Admin', LEVELS.NOTHING),
      cluster,
      groupId,
    })

    await consumer.subscribe({ topic, fromBeginning: true })
    const description = await consumer.describeGroup()

    if (!isConsumerGroupRunning(description)) {
      throw new KafkaJSNonRetriableError(
        `The consumer group must have no running instances, current state: ${description.state}`
      )
    }

    return new Promise((resolve, reject) => {
      consumer.on(consumer.events.FETCH, async () =>
        consumer
          .stop()
          .then(resolve)
          .catch(reject)
      )

      consumer
        .run({
          eachBatchAutoResolve: false,
          eachBatch: async () => true,
        })
        .catch(reject)

      // This consumer doesn't need to consume any data
      consumer.pause([{ topic }])

      for (let seekData of partitions) {
        consumer.seek({ topic, ...seekData })
      }
    })
  }

  /**
   * @param {Array<ResourceConfigQuery>} resources
   * @return {Promise}
   *
   * @typedef {Object} ResourceConfigQuery
   * @property {ResourceType} type
   * @property {string} name
   * @property {Array<String>} [configNames=[]]
   */
  const describeConfigs = async ({ resources }) => {
    if (!resources || !Array.isArray(resources)) {
      throw new KafkaJSNonRetriableError(`Invalid resources array ${resources}`)
    }

    if (resources.length === 0) {
      throw new KafkaJSNonRetriableError('Resources array cannot be empty')
    }

    const validResourceTypes = Object.values(RESOURCE_TYPES)
    const invalidType = resources.find(r => !validResourceTypes.includes(r.type))

    if (invalidType) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource type ${invalidType.type}: ${JSON.stringify(invalidType)}`
      )
    }

    const invalidName = resources.find(r => !r.name || typeof r.name !== 'string')

    if (invalidName) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource name ${invalidName.name}: ${JSON.stringify(invalidName)}`
      )
    }

    const invalidConfigs = resources.find(
      r => !Array.isArray(r.configNames) && r.configNames != null
    )

    if (invalidConfigs) {
      const { configNames } = invalidConfigs
      throw new KafkaJSNonRetriableError(
        `Invalid resource configNames ${configNames}: ${JSON.stringify(invalidConfigs)}`
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        const response = await broker.describeConfigs({ resources })
        return response
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not describe configs', { error: e.message, retryCount, retryTime })
          throw e
        }

        bail(e)
      }
    })
  }

  /**
   * @param {Array<ResourceConfig>} resources
   * @param {boolean} [validateOnly=false]
   * @return {Promise}
   *
   * @typedef {Object} ResourceConfig
   * @property {ResourceType} type
   * @property {string} name
   * @property {Array<ResourceConfigEntry>} configEntries
   *
   * @typedef {Object} ResourceConfigEntry
   * @property {string} name
   * @property {string} value
   */
  const alterConfigs = async ({ resources, validateOnly }) => {
    if (!resources || !Array.isArray(resources)) {
      throw new KafkaJSNonRetriableError(`Invalid resources array ${resources}`)
    }

    if (resources.length === 0) {
      throw new KafkaJSNonRetriableError('Resources array cannot be empty')
    }

    const validResourceTypes = Object.values(RESOURCE_TYPES)
    const invalidType = resources.find(r => !validResourceTypes.includes(r.type))

    if (invalidType) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource type ${invalidType.type}: ${JSON.stringify(invalidType)}`
      )
    }

    const invalidName = resources.find(r => !r.name || typeof r.name !== 'string')

    if (invalidName) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource name ${invalidName.name}: ${JSON.stringify(invalidName)}`
      )
    }

    const invalidConfigs = resources.find(r => !Array.isArray(r.configEntries))

    if (invalidConfigs) {
      const { configEntries } = invalidConfigs
      throw new KafkaJSNonRetriableError(
        `Invalid resource configEntries ${configEntries}: ${JSON.stringify(invalidConfigs)}`
      )
    }

    const invalidConfigValue = resources.find(r =>
      r.configEntries.some(e => typeof e.name !== 'string' || typeof e.value !== 'string')
    )

    if (invalidConfigValue) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource config value: ${JSON.stringify(invalidConfigValue)}`
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        const response = await broker.alterConfigs({ resources, validateOnly: !!validateOnly })
        return response
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not alter configs', { error: e.message, retryCount, retryTime })
          throw e
        }

        bail(e)
      }
    })
  }

  /**
   * Fetch metadata for provided topics.
   *
   * If no topics are provided fetch metadata for all topics of which we are aware.
   * @see https://kafka.apache.org/protocol#The_Messages_Metadata
   *
   * @param {Object} [options]
   * @param {string[]} [options.topics]
   * @return {Promise<TopicsMetadata>}
   *
   * @typedef {Object} TopicsMetadata
   * @property {Array<TopicMetadata>} topics
   *
   * @typedef {Object} TopicMetadata
   * @property {String} name
   * @property {Array<PartitionMetadata>} partitions
   *
   * @typedef {Object} PartitionMetadata
   * @property {number} partitionErrorCode Response error code
   * @property {number} partitionId Topic partition id
   * @property {number} leader  The id of the broker acting as leader for this partition.
   * @property {Array<number>} replicas The set of all nodes that host this partition.
   * @property {Array<number>} isr The set of nodes that are in sync with the leader for this partition.
   */
  const getTopicMetadata = async options => {
    const { topics } = options || {}

    if (topics) {
      await Promise.all(
        topics.map(async topic => {
          if (!topic) {
            throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
          }

          try {
            await cluster.addTargetTopic(topic)
          } catch (e) {
            e.message = `Failed to add target topic ${topic}: ${e.message}`
            throw e
          }
        })
      )
    }

    await cluster.refreshMetadataIfNecessary()
    const targetTopics = topics || [...cluster.targetTopics]

    return {
      topics: await Promise.all(
        targetTopics.map(async topic => ({
          name: topic,
          partitions: await cluster.findTopicPartitionMetadata(topic),
        }))
      ),
    }
  }

  /**
   * @param {string} eventName
   * @param {Function} listener
   * @return {Function}
   */
  const on = (eventName, listener) => {
    if (!eventNames.includes(eventName)) {
      throw new KafkaJSNonRetriableError(`Event name should be one of ${eventKeys}`)
    }

    return instrumentationEmitter.addListener(unwrapEvent(eventName), event => {
      event.type = wrapEvent(event.type)
      Promise.resolve(listener(event)).catch(e => {
        logger.error(`Failed to execute listener: ${e.message}`, {
          eventName,
          stack: e.stack,
        })
      })
    })
  }

  /**
   * @return {Object} logger
   */
  const getLogger = () => logger

  return {
    connect,
    disconnect,
    createTopics,
    deleteTopics,
    getTopicMetadata,
    events,
    fetchOffsets,
    fetchTopicOffsets,
    setOffsets,
    resetOffsets,
    describeConfigs,
    alterConfigs,
    on,
    logger: getLogger,
  }
}
<commit_msg>keep prettier happy<commit_after>const createRetry = require('../retry')
const waitFor = require('../utils/waitFor')
const createConsumer = require('../consumer')
const InstrumentationEventEmitter = require('../instrumentation/emitter')
const { events, wrap: wrapEvent, unwrap: unwrapEvent } = require('./instrumentationEvents')
const { LEVELS } = require('../loggers')
const { KafkaJSNonRetriableError } = require('../errors')
const RESOURCE_TYPES = require('../protocol/resourceTypes')

const { CONNECT, DISCONNECT } = events

const { values, keys } = Object
const eventNames = values(events)
const eventKeys = keys(events)
  .map(key => `admin.events.${key}`)
  .join(', ')

const retryOnLeaderNotAvailable = (fn, opts = {}) => {
  const callback = async () => {
    try {
      return await fn()
    } catch (e) {
      if (e.type !== 'LEADER_NOT_AVAILABLE') {
        throw e
      }
      return false
    }
  }

  return waitFor(callback, opts)
}

const isConsumerGroupRunning = description => ['Empty', 'Dead'].includes(description.state)
const findTopicPartitions = async (cluster, topic) => {
  await cluster.addTargetTopic(topic)
  await cluster.refreshMetadataIfNecessary()

  return cluster
    .findTopicPartitionMetadata(topic)
    .map(({ partitionId }) => partitionId)
    .sort()
}

module.exports = ({
  logger: rootLogger,
  instrumentationEmitter: rootInstrumentationEmitter,
  retry = { retries: 5 },
  cluster,
}) => {
  const logger = rootLogger.namespace('Admin')
  const instrumentationEmitter = rootInstrumentationEmitter || new InstrumentationEventEmitter()

  /**
   * @returns {Promise}
   */
  const connect = async () => {
    await cluster.connect()
    instrumentationEmitter.emit(CONNECT)
  }

  /**
   * @return {Promise}
   */
  const disconnect = async () => {
    await cluster.disconnect()
    instrumentationEmitter.emit(DISCONNECT)
  }

  /**
   * @param {array} topics
   * @param {boolean} [validateOnly=false]
   * @param {number} [timeout=5000]
   * @param {boolean} [waitForLeaders=true]
   * @return {Promise}
   */
  const createTopics = async ({ topics, validateOnly, timeout, waitForLeaders = true }) => {
    if (!topics || !Array.isArray(topics)) {
      throw new KafkaJSNonRetriableError(`Invalid topics array ${topics}`)
    }

    if (topics.filter(({ topic }) => typeof topic !== 'string').length > 0) {
      throw new KafkaJSNonRetriableError(
        'Invalid topics array, the topic names have to be a valid string'
      )
    }

    const topicNames = new Set(topics.map(({ topic }) => topic))
    if (topicNames.size < topics.length) {
      throw new KafkaJSNonRetriableError(
        'Invalid topics array, it cannot have multiple entries for the same topic'
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        await broker.createTopics({ topics, validateOnly, timeout })

        if (waitForLeaders) {
          const topicNamesArray = Array.from(topicNames.values())
          await retryOnLeaderNotAvailable(async () => await broker.metadata(topicNamesArray), {
            delay: 100,
            timeoutMessage: 'Timed out while waiting for topic leaders',
          })
        }

        return true
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not create topics', { error: e.message, retryCount, retryTime })
          throw e
        }

        if (e.type === 'TOPIC_ALREADY_EXISTS') {
          return false
        }

        bail(e)
      }
    })
  }

  /**
   * @param {string[]} topics
   * @param {number} [timeout=5000]
   * @return {Promise}
   */
  const deleteTopics = async ({ topics, timeout }) => {
    if (!topics || !Array.isArray(topics)) {
      throw new KafkaJSNonRetriableError(`Invalid topics array ${topics}`)
    }

    if (topics.filter(topic => typeof topic !== 'string').length > 0) {
      throw new KafkaJSNonRetriableError('Invalid topics array, the names must be a valid string')
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        await broker.deleteTopics({ topics, timeout })

        // Remove deleted topics
        for (let topic of topics) {
          cluster.targetTopics.delete(topic)
        }

        await cluster.refreshMetadata()
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not delete topics', { error: e.message, retryCount, retryTime })
          throw e
        }

        if (e.type === 'REQUEST_TIMED_OUT') {
          logger.error(
            'Could not delete topics, check if "delete.topic.enable" is set to "true" (the default value is "false") or increase the timeout',
            {
              error: e.message,
              retryCount,
              retryTime,
            }
          )
        }

        bail(e)
      }
    })
  }

  /**
   * @param {string} topic
   */

  const fetchTopicOffsets = async topic => {
    if (!topic || typeof topic !== 'string') {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const topicPartitions = await findTopicPartitions(cluster, topic)
    const partitions = topicPartitions.map(partition => ({ partition }))
    const broker = await cluster.findControllerBroker()
    const offsets = await broker.listOffsets({ topics: [{ topic, partitions }] })

    return offsets.responses
      .find(response => response.topic === topic)
      .partitions.map(({ partition, offset }) => ({ partition, offset }))
  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @return {Promise}
   */
  const fetchOffsets = async ({ groupId, topic }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const partitions = await findTopicPartitions(cluster, topic)
    const coordinator = await cluster.findGroupCoordinator({ groupId })
    const partitionsToFetch = partitions.map(partition => ({ partition }))

    const { responses } = await coordinator.offsetFetch({
      groupId,
      topics: [{ topic, partitions: partitionsToFetch }],
    })

    return responses
      .filter(response => response.topic === topic)
      .map(({ partitions }) => partitions.map(({ partition, offset }) => ({ partition, offset })))
      .pop()
  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @param {boolean} [earliest=false]
   * @return {Promise}
   */
  const resetOffsets = async ({ groupId, topic, earliest = false }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    const partitions = await findTopicPartitions(cluster, topic)
    const partitionsToSeek = partitions.map(partition => ({
      partition,
      offset: cluster.defaultOffset({ fromBeginning: earliest }),
    }))

    return setOffsets({ groupId, topic, partitions: partitionsToSeek })
  }

  /**
   * @param {string} groupId
   * @param {string} topic
   * @param {Array<SeekEntry>} partitions
   * @return {Promise}
   *
   * @typedef {Object} SeekEntry
   * @property {number} partition
   * @property {string} offset
   */
  const setOffsets = async ({ groupId, topic, partitions }) => {
    if (!groupId) {
      throw new KafkaJSNonRetriableError(`Invalid groupId ${groupId}`)
    }

    if (!topic) {
      throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
    }

    if (!partitions || partitions.length === 0) {
      throw new KafkaJSNonRetriableError(`Invalid partitions`)
    }

    const consumer = createConsumer({
      logger: rootLogger.namespace('Admin', LEVELS.NOTHING),
      cluster,
      groupId,
    })

    await consumer.subscribe({ topic, fromBeginning: true })
    const description = await consumer.describeGroup()

    if (!isConsumerGroupRunning(description)) {
      throw new KafkaJSNonRetriableError(
        `The consumer group must have no running instances, current state: ${description.state}`
      )
    }

    return new Promise((resolve, reject) => {
      consumer.on(consumer.events.FETCH, async () =>
        consumer
          .stop()
          .then(resolve)
          .catch(reject)
      )

      consumer
        .run({
          eachBatchAutoResolve: false,
          eachBatch: async () => true,
        })
        .catch(reject)

      // This consumer doesn't need to consume any data
      consumer.pause([{ topic }])

      for (let seekData of partitions) {
        consumer.seek({ topic, ...seekData })
      }
    })
  }

  /**
   * @param {Array<ResourceConfigQuery>} resources
   * @return {Promise}
   *
   * @typedef {Object} ResourceConfigQuery
   * @property {ResourceType} type
   * @property {string} name
   * @property {Array<String>} [configNames=[]]
   */
  const describeConfigs = async ({ resources }) => {
    if (!resources || !Array.isArray(resources)) {
      throw new KafkaJSNonRetriableError(`Invalid resources array ${resources}`)
    }

    if (resources.length === 0) {
      throw new KafkaJSNonRetriableError('Resources array cannot be empty')
    }

    const validResourceTypes = Object.values(RESOURCE_TYPES)
    const invalidType = resources.find(r => !validResourceTypes.includes(r.type))

    if (invalidType) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource type ${invalidType.type}: ${JSON.stringify(invalidType)}`
      )
    }

    const invalidName = resources.find(r => !r.name || typeof r.name !== 'string')

    if (invalidName) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource name ${invalidName.name}: ${JSON.stringify(invalidName)}`
      )
    }

    const invalidConfigs = resources.find(
      r => !Array.isArray(r.configNames) && r.configNames != null
    )

    if (invalidConfigs) {
      const { configNames } = invalidConfigs
      throw new KafkaJSNonRetriableError(
        `Invalid resource configNames ${configNames}: ${JSON.stringify(invalidConfigs)}`
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        const response = await broker.describeConfigs({ resources })
        return response
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not describe configs', { error: e.message, retryCount, retryTime })
          throw e
        }

        bail(e)
      }
    })
  }

  /**
   * @param {Array<ResourceConfig>} resources
   * @param {boolean} [validateOnly=false]
   * @return {Promise}
   *
   * @typedef {Object} ResourceConfig
   * @property {ResourceType} type
   * @property {string} name
   * @property {Array<ResourceConfigEntry>} configEntries
   *
   * @typedef {Object} ResourceConfigEntry
   * @property {string} name
   * @property {string} value
   */
  const alterConfigs = async ({ resources, validateOnly }) => {
    if (!resources || !Array.isArray(resources)) {
      throw new KafkaJSNonRetriableError(`Invalid resources array ${resources}`)
    }

    if (resources.length === 0) {
      throw new KafkaJSNonRetriableError('Resources array cannot be empty')
    }

    const validResourceTypes = Object.values(RESOURCE_TYPES)
    const invalidType = resources.find(r => !validResourceTypes.includes(r.type))

    if (invalidType) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource type ${invalidType.type}: ${JSON.stringify(invalidType)}`
      )
    }

    const invalidName = resources.find(r => !r.name || typeof r.name !== 'string')

    if (invalidName) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource name ${invalidName.name}: ${JSON.stringify(invalidName)}`
      )
    }

    const invalidConfigs = resources.find(r => !Array.isArray(r.configEntries))

    if (invalidConfigs) {
      const { configEntries } = invalidConfigs
      throw new KafkaJSNonRetriableError(
        `Invalid resource configEntries ${configEntries}: ${JSON.stringify(invalidConfigs)}`
      )
    }

    const invalidConfigValue = resources.find(r =>
      r.configEntries.some(e => typeof e.name !== 'string' || typeof e.value !== 'string')
    )

    if (invalidConfigValue) {
      throw new KafkaJSNonRetriableError(
        `Invalid resource config value: ${JSON.stringify(invalidConfigValue)}`
      )
    }

    const retrier = createRetry(retry)

    return retrier(async (bail, retryCount, retryTime) => {
      try {
        await cluster.refreshMetadata()
        const broker = await cluster.findControllerBroker()
        const response = await broker.alterConfigs({ resources, validateOnly: !!validateOnly })
        return response
      } catch (e) {
        if (e.type === 'NOT_CONTROLLER') {
          logger.warn('Could not alter configs', { error: e.message, retryCount, retryTime })
          throw e
        }

        bail(e)
      }
    })
  }

  /**
   * Fetch metadata for provided topics.
   *
   * If no topics are provided fetch metadata for all topics of which we are aware.
   * @see https://kafka.apache.org/protocol#The_Messages_Metadata
   *
   * @param {Object} [options]
   * @param {string[]} [options.topics]
   * @return {Promise<TopicsMetadata>}
   *
   * @typedef {Object} TopicsMetadata
   * @property {Array<TopicMetadata>} topics
   *
   * @typedef {Object} TopicMetadata
   * @property {String} name
   * @property {Array<PartitionMetadata>} partitions
   *
   * @typedef {Object} PartitionMetadata
   * @property {number} partitionErrorCode Response error code
   * @property {number} partitionId Topic partition id
   * @property {number} leader  The id of the broker acting as leader for this partition.
   * @property {Array<number>} replicas The set of all nodes that host this partition.
   * @property {Array<number>} isr The set of nodes that are in sync with the leader for this partition.
   */
  const getTopicMetadata = async options => {
    const { topics } = options || {}

    if (topics) {
      await Promise.all(
        topics.map(async topic => {
          if (!topic) {
            throw new KafkaJSNonRetriableError(`Invalid topic ${topic}`)
          }

          try {
            await cluster.addTargetTopic(topic)
          } catch (e) {
            e.message = `Failed to add target topic ${topic}: ${e.message}`
            throw e
          }
        })
      )
    }

    await cluster.refreshMetadataIfNecessary()
    const targetTopics = topics || [...cluster.targetTopics]

    return {
      topics: await Promise.all(
        targetTopics.map(async topic => ({
          name: topic,
          partitions: await cluster.findTopicPartitionMetadata(topic),
        }))
      ),
    }
  }

  /**
   * @param {string} eventName
   * @param {Function} listener
   * @return {Function}
   */
  const on = (eventName, listener) => {
    if (!eventNames.includes(eventName)) {
      throw new KafkaJSNonRetriableError(`Event name should be one of ${eventKeys}`)
    }

    return instrumentationEmitter.addListener(unwrapEvent(eventName), event => {
      event.type = wrapEvent(event.type)
      Promise.resolve(listener(event)).catch(e => {
        logger.error(`Failed to execute listener: ${e.message}`, {
          eventName,
          stack: e.stack,
        })
      })
    })
  }

  /**
   * @return {Object} logger
   */
  const getLogger = () => logger

  return {
    connect,
    disconnect,
    createTopics,
    deleteTopics,
    getTopicMetadata,
    events,
    fetchOffsets,
    fetchTopicOffsets,
    setOffsets,
    resetOffsets,
    describeConfigs,
    alterConfigs,
    on,
    logger: getLogger,
  }
}
<|endoftext|>


<commit_before>/* globals define, app, ajaxify, bootbox, socket, templates, utils */

$(document).ready(function () {
    'use strict';

    require([
        'translator'
    ], function (translator) {

        var events          = {
                'event:voted' : likesDidUpdate,
                'posts.upvote': likeDidChange,
                'posts.unvote': likeDidChange
            }, components = {
                TOGGLE_BUTTON: 'ns/likes/toggle',
                COUNT_BUTTON : 'ns/likes/count',
                USER_LIST    : 'ns/likes/users'
            }, previewLimit = 3, Color = net.brehaut.Color,
            initColor       = '#9e9e9e', targetColor = '#ff0000', totalToColor = 8;

        $(window).on('action:ajaxify.start', function (ev, data) {
            if (ajaxify.currentPage !== data.url) {
                setSubscription(false);
            }
        });

        $(window).on('action:topic.loading', function (e) {
            //To be sure, that we subscribed only once
            setSubscription(false);
            setSubscription(true);
            addListeners(
                $(getComponentSelector(components.TOGGLE_BUTTON)),
                $(getComponentSelector(components.COUNT_BUTTON))
            );
        });

        $(window).on('action:posts.loaded', function (e, data) {
            data.posts.forEach(function (post, index) {
                addListeners(
                    getComponentByPostId(post.pid, components.TOGGLE_BUTTON),
                    getComponentByPostId(post.pid, components.COUNT_BUTTON)
                );
            });
        });

        function addListeners($toggleButtons, $countButtons) {
            $toggleButtons.on('click', function (e) {
                var $el = $(this);
                toggleLike(getPostId($el), $el.hasClass('liked'));
            });

            $countButtons.on('click', function (e) {
                showVotersFor(getPostId($(this)));
            });
        }

        function getComponentByPostId(pid, componentType) {
            return $('[data-pid="' + pid + '"]').find(getComponentSelector(componentType));
        }

        function getComponentSelector(componentType) {
            return '[component="' + componentType + '"]';
        }

        function getPostId($child) {
            return $child.parents('[data-pid]').attr('data-pid');
        }

        /**
         * Triggered on like/unliked
         * @param data {object} Fields: {downvote:false,post:{pid:"14",uid: 4,votes:2},upvote:true,user:{reputation:3}}
         */
        function likeDidChange(data) {
            var label = data.upvote ? ' Unlike' : ' Like';
            getComponentByPostId(data.post.pid, components.TOGGLE_BUTTON).toggleClass('liked', data.upvote).text(label);
        }

        /**
         * Triggered on Like entity update
         * @param data {object} Same signature as for like/unliked handler
         */
        function likesDidUpdate(data) {
            var votes = data.post.votes;
            getComponentByPostId(data.post.pid, components.COUNT_BUTTON).text(votes).data('likes', votes);
            //TODO Re-render list only if user is interested in it
            //showVotersFor(data.post.pid);
        }

        function renderVoters($el, votersData) {
            var usernames = votersData.usernames, len = usernames.length, previewUsernames, renderNames, count = votersData.otherCount;
            if (len + count > previewLimit) {
                previewUsernames = usernames.slice(0, previewLimit);
                count = count + len - previewLimit;
                //Commas are not allowed from translation
                renderNames = previewUsernames.join(', ').replace(/,/g, '|');
                translator.translate('[[topic:users_and_others, ' + renderNames + ', ' + count + ']]', function (translated) {
                    translated = translated.replace(/\|/g, ',');
                    $el.text(translated);
                });
            } else {
                $el.text(usernames.join(', '));
            }

            $el
                .css('opacity', 0)
                .animate({'opacity': 1}, 200);
        }

        function showVotersFor(pid) {
            socket.emit('posts.getUpvoters', [pid], function (error, data) {
                if (!error && data.length) {
                    renderVoters(getComponentByPostId(pid, components.USER_LIST), data[0]);
                }
            });
        }

        function setSubscription(state) {
            var method = state ? 'on' : 'removeListener';
            for (var socketEvent in events) {
                socket[method](socketEvent, events[socketEvent]);
            }
        }

        function toggleLike(pid, liked) {
            socket.emit(liked ? 'posts.unvote' : 'posts.upvote', {
                pid    : pid,
                room_id: app.currentRoom
            }, function (error) {
                if (error) {
                    app.alertError(error.message);
                }
            });
        }
    });
});<commit_msg>evaluation method<commit_after>/* globals define, app, ajaxify, bootbox, socket, templates, utils */

$(document).ready(function () {
    'use strict';

    require([
        'translator'
    ], function (translator) {

        var events          = {
                'event:voted' : likesDidUpdate,
                'posts.upvote': likeDidChange,
                'posts.unvote': likeDidChange
            }, components = {
                TOGGLE_BUTTON: 'ns/likes/toggle',
                COUNT_BUTTON : 'ns/likes/count',
                USER_LIST    : 'ns/likes/users'
            }, previewLimit = 3, Color = net.brehaut.Color,
            initColor       = '#9e9e9e', targetColor = '#ff0000', totalToColor = 8;

        $(window).on('action:ajaxify.start', function (ev, data) {
            if (ajaxify.currentPage !== data.url) {
                setSubscription(false);
            }
        });

        $(window).on('action:topic.loading', function (e) {
            //To be sure, that we subscribed only once
            setSubscription(false);
            setSubscription(true);
            addListeners(
                $(getComponentSelector(components.TOGGLE_BUTTON)),
                $(getComponentSelector(components.COUNT_BUTTON))
            );
        });

        $(window).on('action:posts.loaded', function (e, data) {
            data.posts.forEach(function (post, index) {
                addListeners(
                    getComponentByPostId(post.pid, components.TOGGLE_BUTTON),
                    getComponentByPostId(post.pid, components.COUNT_BUTTON)
                );
            });
        });

        function addListeners($toggleButtons, $countButtons) {
            $toggleButtons.on('click', function (e) {
                var $el = $(this);
                toggleLike(getPostId($el), $el.hasClass('liked'));
            });

            $countButtons.on('click', function (e) {
                showVotersFor(getPostId($(this)));
            });
        }

        function evaluateVotesNumber($button, votes) {
            $button.text(votes);
            $button.css(
                'color',
                Color(initColor).blend(Color(targetColor), votes / totalToColor).toCSS());
        }

        function getComponentByPostId(pid, componentType) {
            return $('[data-pid="' + pid + '"]').find(getComponentSelector(componentType));
        }

        function getComponentSelector(componentType) {
            return '[component="' + componentType + '"]';
        }

        function getPostId($child) {
            return $child.parents('[data-pid]').attr('data-pid');
        }

        /**
         * Triggered on like/unliked
         * @param data {object} Fields: {downvote:false,post:{pid:"14",uid: 4,votes:2},upvote:true,user:{reputation:3}}
         */
        function likeDidChange(data) {
            var label = data.upvote ? ' Unlike' : ' Like';
            getComponentByPostId(data.post.pid, components.TOGGLE_BUTTON).toggleClass('liked', data.upvote).text(label);
        }

        /**
         * Triggered on Like entity update
         * @param data {object} Same signature as for like/unliked handler
         */
        function likesDidUpdate(data) {
            var votes = data.post.votes;
            var button = getComponentByPostId(data.post.pid, components.COUNT_BUTTON);
            button.data('likes', votes);
            evaluateVotesNumber(button, votes);
            //TODO Re-render list only if user is interested in it
            //showVotersFor(data.post.pid);
        }

        function renderVoters($el, votersData) {
            var usernames = votersData.usernames, len = usernames.length, previewUsernames, renderNames, count = votersData.otherCount;
            if (len + count > previewLimit) {
                previewUsernames = usernames.slice(0, previewLimit);
                count = count + len - previewLimit;
                //Commas are not allowed from translation
                renderNames = previewUsernames.join(', ').replace(/,/g, '|');
                translator.translate('[[topic:users_and_others, ' + renderNames + ', ' + count + ']]', function (translated) {
                    translated = translated.replace(/\|/g, ',');
                    $el.text(translated);
                });
            } else {
                $el.text(usernames.join(', '));
            }

            $el
                .css('opacity', 0)
                .animate({'opacity': 1}, 200);
        }

        function showVotersFor(pid) {
            socket.emit('posts.getUpvoters', [pid], function (error, data) {
                if (!error && data.length) {
                    renderVoters(getComponentByPostId(pid, components.USER_LIST), data[0]);
                }
            });
        }

        function setSubscription(state) {
            var method = state ? 'on' : 'removeListener';
            for (var socketEvent in events) {
                socket[method](socketEvent, events[socketEvent]);
            }
        }

        function toggleLike(pid, liked) {
            socket.emit(liked ? 'posts.unvote' : 'posts.upvote', {
                pid    : pid,
                room_id: app.currentRoom
            }, function (error) {
                if (error) {
                    app.alertError(error.message);
                }
            });
        }
    });
});<|endoftext|>


<commit_before>var Flow = require('./flow/flow');
var socket = require('./socket');

module.exports = function factory (config) {

    return socket.incoming(Flow({

        module: function loadModule (name, callback) {
            try {
                name = name[0] === '/' ? name : config.paths.module + '/' + name;
                var module = require(name);
            } catch (err) {
                return callback(err);
            }

            callback(null, module);
        },

        composition: function (name, callback) {
            try {
                var composition = require(this.config.paths.composition + '/' + name + '.json');
            } catch (err) {
                return callback(err);
            }

            if (composition.module !== 'string') {
                composition.module = this.config.paths.public + composition.module.main;
            }

            callback(null, composition);
        },

        request: socket.request,
        config: config,
        clientComposition: composition,
        // TODO maybe there's a better way to provide a core flow config
        _flow: {
            'C': [[':clientComposition']]
        }
    }));
};

function composition (options, name, next) {

    name = name.toString();

    // handle entrypoinut
    if (name === '*') {
        var host = '';//instance = socket.upgradeReq.headers.host.split(':')[0];
        var entrypoints = this.config.entrypoints[options.session.role ? 'private' : 'public'];

        // TODO maybe entrypoints can have simple routing..
        name = entrypoints[host] || entrypoints['*'];
    }

    if (!name) {
        return next(new Error('Flow.server.composition: No name.'));
    }

    try {
        var composition = require(this.config.paths.composition + '/' + name + '.json');
    } catch (err) {
        return next(err);
    }

    if (!composition.module) {
        return next(new Error('Flow.server.composition: No module field on instance "' + name  + '".'));
    }

    // handle custom modules
    if (typeof composition.module !== 'string') {
        composition.module = composition.module.browser || composition.module.main;
    } else {
        composition.module = composition.module;
    }

    next(null, JSON.stringify(composition));
}
<commit_msg>extend core instance with multiplexer stram<commit_after>var Flow = require('./flow/flow');
var socket = require('./socket');

module.exports = function factory (config) {

    return Flow({

        module: function loadModule (name, callback) {
            try {
                name = name[0] === '/' ? name : config.paths.module + '/' + name;
                var module = require(name);
            } catch (err) {
                return callback(err);
            }

            callback(null, module);
        },

        composition: function (name, callback) {
            try {
                var composition = require(this.config.paths.composition + '/' + name + '.json');
            } catch (err) {
                return callback(err);
            }

            if (composition.module !== 'string') {
                composition.module = this.config.paths.public + composition.module.main;
            }

            callback(null, composition);
        },

        mux: socket.mux,
        request: socket.request,
        config: config,
        clientComposition: composition,
        // TODO maybe there's a better way to provide a core flow config
        _flow: {
            'C': [[':clientComposition']]
        }
    });
};

function composition (options, name, next) {

    name = name.toString();

    // handle entrypoinut
    if (name === '*') {
        var host = '';//instance = socket.upgradeReq.headers.host.split(':')[0];
        var entrypoints = this.config.entrypoints[options.session.role ? 'private' : 'public'];

        // TODO maybe entrypoints can have simple routing..
        name = entrypoints[host] || entrypoints['*'];
    }

    if (!name) {
        return next(new Error('Flow.server.composition: No name.'));
    }

    try {
        var composition = require(this.config.paths.composition + '/' + name + '.json');
    } catch (err) {
        return next(err);
    }

    if (!composition.module) {
        return next(new Error('Flow.server.composition: No module field on instance "' + name  + '".'));
    }

    // handle custom modules
    if (typeof composition.module !== 'string') {
        composition.module = composition.module.browser || composition.module.main;
    } else {
        composition.module = composition.module;
    }

    next(null, JSON.stringify(composition));
}

<|endoftext|>


<commit_before>import React from 'react'
import { api } from '../../data'
import { Field } from '../components/Forms'
import { Text, Column, Row } from 'rebass'
import yup from 'yup'
import { cx } from '../theme'
import styled from 'styled-components'

const InviteLink = styled.a`
  color: ${cx('primary')};
  background: white;
  cursor: pointer;
`

const InnerForm = props => {
  const handleChange = e => {
    if (e.keyCode === 13) {
      e.preventDefault()
      handleSubmit()
    }
  }
  const handleSubmit = () => {
    const leaderInvite = document.querySelector('#leader_invite')
    const schema = yup.object().shape({
      email: yup
        .string()
        .required('required')
        .email('email')
    })

    schema
      .validate({ email: leaderInvite.value })
      .then(data => {
        fetch(`${api}/v1/new_club_applications/${props.id}/add_applicant`, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${props.authToken}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(data)
        })
          .then(res => {
            if (res.ok) {
              return res.json()
            } else {
              throw res
            }
          })
          .then(json => {
            alert(`Invite sent to ${data.email}!`)
            leaderInvite.value = ''
          })
          .catch(e => {
            alert(e)
          })
      })
      .catch(e => {
        console.log(e)
        if (e.type === 'required') {
          // skip
        } else if (e.message === 'email') {
          alert('Invalid co-leader email')
        } else {
          alert('Something has gone terribly wrong')
        }
      })
  }

  return (
    <Row>
      <Column width={3 / 5}>
        <Text>
          Please provide the email addresses of the other club leaders
        </Text>
        <Field
          id="leader_invite"
          onKeyDown={handleChange}
          p="Co-leader's email"
        />
      </Column>
      <Column style={{ alignSelf: 'flex-end' }}>
        <InviteLink onClick={handleSubmit}>Add co-lead</InviteLink>
      </Column>
    </Row>
  )
}

export default InnerForm
<commit_msg>Redesign LeaderInviteForm with new IconButton<commit_after>import React from 'react'
import { api } from '../../data'
import { Field } from '../components/Forms'
import { Box, Flex, Text, IconButton, cx } from '@hackclub/design-system'
import yup from 'yup'

const InnerForm = props => {
  const handleChange = e => {
    if (e.keyCode === 13) {
      e.preventDefault()
      handleSubmit()
    }
  }

  const handleSubmit = () => {
    const leaderInvite = document.querySelector('#leader_invite')
    const schema = yup.object().shape({
      email: yup
        .string()
        .required('required')
        .email('email')
    })

    schema
      .validate({ email: leaderInvite.value })
      .then(data => {
        fetch(`${api}/v1/new_club_applications/${props.id}/add_applicant`, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${props.authToken}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(data)
        })
          .then(res => {
            return res.json()
            if (!res.ok) throw res
          })
          .then(json => {
            alert(`Invite sent to ${data.email}!`)
            leaderInvite.value = ''
          })
          .catch(e => {
            console.error(e.errors)
            alert(e)
          })
      })
      .catch(e => {
        console.log(e)
        if (e.type === 'required') {
          // skip
        } else if (e.message === 'email') {
          alert('Invalid co-leader email')
        } else {
          alert('Something has gone terribly wrong')
        }
      })
  }

  return (
    <Flex align="flex-end" mb={2}>
      <Field
        id="leader_invite"
        onKeyDown={handleChange}
        label="Please provide the email addresses of the other club leaders"
        p="Co-leader's email"
      />
      <IconButton
        name="add"
        bg="success"
        color="white"
        size={24}
        ml={3}
        p={1}
        mb={2}
        circle
        onClick={handleSubmit}
        style={{ flexShrink: 'none' }}
      />
    </Flex>
  )
}

export default InnerForm
<|endoftext|>


<commit_before>import m from 'mithril';
import FastClick from 'fastclick';
import AppComponent from './components/app.js';

// Eliminate the #! for all routes
m.route.prefix('');

m.route(document.querySelector('main'), '/', {
  '/': AppComponent,
  '/room/:roomCode': AppComponent
});

FastClick.attach(document.body);
<commit_msg>Fix Mithril v2 bug with m.route.prefix no longer being a function<commit_after>import m from 'mithril';
import FastClick from 'fastclick';
import AppComponent from './components/app.js';

// Eliminate the #! for all routes
m.route.prefix = '';

m.route(document.querySelector('main'), '/', {
  '/': AppComponent,
  '/room/:roomCode': AppComponent
});

FastClick.attach(document.body);
<|endoftext|>


<commit_before>// @flow
import { getVersionInfo } from '../versionInfo';

import { IProvider, IVersionAndStoreUrl } from './types';

export type PlayStoreGetVersionOption = {
  packageName?: string,
  fetchOptions?: any,
  ignoreErrors?: boolean,
};

export interface IPlayStoreProvider extends IProvider {
  getVersion: PlayStoreGetVersionOption => Promise<IVersionAndStoreUrl>;
}

function error(text: string) {
  return {
    message:
      "Parse Error. Your app's play store page doesn't seem to have latest app version info.",
    text,
  };
}

class PlayStoreProvider implements IProvider {
  getVersion(option: PlayStoreGetVersionOption): Promise<IVersionAndStoreUrl> {
    const opt = option || {};
    try {
      if (!opt.packageName) {
        opt.packageName = getVersionInfo().getPackageName();
      }

      const storeUrl = `https://play.google.com/store/apps/details?id=${opt.packageName}&hl=en`;

      return fetch(storeUrl, opt.fetchOptions)
        .then(res => res.text())
        .then(text => {
          const match = text.match(/Current Version.+>([\d.]+)<\/span>/);
          if (match) {
            const latestVersion = match[1].trim();

            return Promise.resolve({ version: latestVersion, storeUrl });
          }

          return Promise.reject(error(text));
        });
    } catch (e) {
      if (opt.ignoreErrors) {
        console.warn(e); // eslint-disable-line no-console
      } else {
        throw e;
      }
    }
  }
}

export default new PlayStoreProvider();
<commit_msg>Resolve issue with overly greedy regex capturing entire response (#111)<commit_after>// @flow
import { getVersionInfo } from '../versionInfo';

import { IProvider, IVersionAndStoreUrl } from './types';

export type PlayStoreGetVersionOption = {
  packageName?: string,
  fetchOptions?: any,
  ignoreErrors?: boolean,
};

export interface IPlayStoreProvider extends IProvider {
  getVersion: PlayStoreGetVersionOption => Promise<IVersionAndStoreUrl>;
}

function error(text: string) {
  return {
    message:
      "Parse Error. Your app's play store page doesn't seem to have latest app version info.",
    text,
  };
}

class PlayStoreProvider implements IProvider {
  getVersion(option: PlayStoreGetVersionOption): Promise<IVersionAndStoreUrl> {
    const opt = option || {};
    try {
      if (!opt.packageName) {
        opt.packageName = getVersionInfo().getPackageName();
      }

      const storeUrl = `https://play.google.com/store/apps/details?id=${opt.packageName}&hl=en`;

      return fetch(storeUrl, opt.fetchOptions)
        .then(res => res.text())
        .then(text => {
          const match = text.match(/Current Version.+?>([\d.]+)<\/span>/);
          if (match) {
            const latestVersion = match[1].trim();

            return Promise.resolve({ version: latestVersion, storeUrl });
          }

          return Promise.reject(error(text));
        });
    } catch (e) {
      if (opt.ignoreErrors) {
        console.warn(e); // eslint-disable-line no-console
      } else {
        throw e;
      }
    }
  }
}

export default new PlayStoreProvider();
<|endoftext|>


<commit_before>define([
	"engine/rendering/texturemapping",
	"engine/rendering/image"
], function(TextureMapping, Image) {

	var SmartTexture = mini.Class.subclass({
		initialize: function(imagePath, path) {
			this.image = Image.get(imagePath);
			this.path = path;

			this.description = {
				pattern: {
					image: {
						x: 0,
						y: 0,
						width: 50,
						height: 20
					},
					scale: {
						x: 1,
						y: 1
					}
				}
			};
			var dg = new dat.gui.GUI();
			dg.add(this.description.pattern.scale, "x").min(-2.1).max(2.1).step(0.01);
			dg.add(this.description.pattern.scale, "y").min(-2.1).max(2.1).step(0.01)
		},

		_getPatternImage: function() {
			var patternCanvas = document.createElement('canvas');
			patternCanvas.width = this.description.pattern.image.width;
			patternCanvas.height = this.description.pattern.image.height;

			patternContext = patternCanvas.getContext('2d');
			patternContext.drawImage(
				this.image.data,
				this.description.pattern.image.x,
				this.description.pattern.image.y,
				this.description.pattern.image.width,
				this.description.pattern.image.height,
				0,
				0,
				this.description.pattern.image.width,
				this.description.pattern.image.height
			);

			return patternCanvas;
		},

		draw: function(entity, renderer, layer) {
			var context = renderer.context;

			var patterImage = this._getPatternImage();
			var pattern = context.createPattern(patterImage, 'repeat');

			context.save();

			context.beginPath();
			renderer.applyPathToContext(this.path, context);
			context.closePath();
			context.clip();

			// TODO: scale with canvas size
			context.scale(
					env.camera.viewport.extent.x,
					env.camera.viewport.extent.y
				);
			context.scale(1, -1);
			context.scale(
					1 / env.canvas.extent.x,
					1 / env.canvas.extent.y
				);

			// texture-specific scaling
			context.scale(
				this.description.pattern.scale.x,
				this.description.pattern.scale.y
			);

			context.fillStyle = pattern;
			context.fill();

			context.restore();

			this.path.draw("violet");
		}
	});

	return SmartTexture;
});
<commit_msg>extracted _drawPattern<commit_after>define([
	"engine/rendering/texturemapping",
	"engine/rendering/image"
], function(TextureMapping, Image) {

	var SmartTexture = mini.Class.subclass({
		initialize: function(imagePath, path) {
			this.image = Image.get(imagePath);
			this.path = path;

			this.description = {
				pattern: {
					image: {
						x: 0,
						y: 0,
						width: 50,
						height: 20
					},
					scale: {
						x: 1,
						y: 1
					}
				}
			};
			var dg = new dat.gui.GUI();
			dg.add(this.description.pattern.scale, "x").min(-2.1).max(2.1).step(0.01);
			dg.add(this.description.pattern.scale, "y").min(-2.1).max(2.1).step(0.01)
		},

		draw: function(entity, renderer, layer) {
			this._drawPattern(renderer);
			this.path.draw("violet");
		},

		_drawPattern: function(renderer) {
			var context = renderer.context;

			var patterImage = this._getPatternImage();
			var pattern = context.createPattern(patterImage, 'repeat');

			context.save();

			context.beginPath();
			renderer.applyPathToContext(this.path, context);
			context.closePath();
			context.clip();

			// TODO: scale with canvas size
			context.scale(
					env.camera.viewport.extent.x,
					env.camera.viewport.extent.y
				);
			context.scale(1, -1);
			context.scale(
					1 / env.canvas.extent.x,
					1 / env.canvas.extent.y
				);

			// texture-specific scaling
			context.scale(
				this.description.pattern.scale.x,
				this.description.pattern.scale.y
			);

			context.fillStyle = pattern;
			context.fill();

			context.restore();

			this.path.draw("violet");
		},

		_getPatternImage: function() {
			var patternCanvas = document.createElement('canvas');
			patternCanvas.width = this.description.pattern.image.width;
			patternCanvas.height = this.description.pattern.image.height;

			patternContext = patternCanvas.getContext('2d');
			patternContext.drawImage(
				this.image.data,
				this.description.pattern.image.x,
				this.description.pattern.image.y,
				this.description.pattern.image.width,
				this.description.pattern.image.height,
				0,
				0,
				this.description.pattern.image.width,
				this.description.pattern.image.height
			);

			return patternCanvas;
		}
	});

	return SmartTexture;
});
<|endoftext|>


<commit_before>var $debug = true
var _preOps = [ '+', '-', '*', '/', '%', '.', '$', '!' ]; // may be used before colon to form special operator

var _tokenize = function (raw) {

  /** tokenizer **/
  raw = raw.replace(/;/g, "; ")
  raw = raw.replace(/{/g, " {") // why?  because leaving out space in this causes error:  .someclass{
  var tokens = raw.match(/\S+/g)
  if ($debug) console.log({tokens1: tokens})

  // TODO: integrate following block into previous RegExp.match
  // pop the biop (ex: .:, +:, *:) off the end of some tokens, add to new token
  var temp = []
  var len = tokens.length
  for (var i = 0; i < len; i++ ) {
    var tok = tokens[i]
    // if token has a `:`
    if (tok.slice(-1) == ':') {
        withoutColon = tok.slice(0, -1); // remove the ":" from end
        preOp = '';
        withoutOp = withoutColon;

        // is the current last char a preOp char?
        lastChar = withoutColon.slice(-1);
        if (_preOps.indexOf( lastChar ) != -1) {
            // yes it is.  remove it
            preOp = withoutColon.slice(-1);
            withoutOp = withoutColon.slice(0, -1);
        }
        op = preOp + ':';
        temp.push(withoutOp);
        temp.push(op);

    }
    // if the token has `;`
    else if ( tok.indexOf(';') >= 0) {
        mid = tok.indexOf(';')
        temp.push( tok.slice(0, mid) )
        temp.push( ';')

        rgtHalf = tok.slice(mid + 1).trim()
        if (rgtHalf) temp.push(rgtHalf)
    }
    // if the token has `(` or `)`
    else if ( tok.indexOf('(') != -1 || tok.indexOf(')') != -1) {
        // first the `(`
        mid = tok.indexOf('(')
        if (mid != -1) {
            temp.push( tok.slice(0, mid) )
            temp.push('(')
        }

        // next the `)`
        rgtHalf = tok.slice(mid + 1)
        if (rgtHalf.indexOf(')') != -1) {
            mid = rgtHalf.indexOf(')')
            temp.push( rgtHalf.slice(0, mid) )
            temp.push(')')
        }
        else {
            temp.push( rgtHalf )
        }
    }
    else {
        temp.push(tok)
    }
  }
  tokens = temp
  if ($debug) console.log({tokens2: tokens})

  return tokens;
}

/**
 * this fn makes sure that operators are stuck together
 */
var _grouper = function (tokens) {

    groups = []
    combined = ''
    len = tokens.length

    for ( var i = 0; i < len; i++ ) {
        tok = tokens[i]
        if (
            (tok.length == 2 && tok.charAt(1) == ':') ||
            (['{', '}', ':', ';', '(', ')'].indexOf( tok ) != -1)
        ) {
            if (combined) groups.push( combined.trim() )
            combined = ''
            groups.push( tok )
        }
        else {

            combined = combined + " " + tok
        }
    }

    return groups
}

var _categorizer = function(tokens) {
    cats = []

    if (tokens) {
        prev = ''
        next = ''
        // determine key, lft, rgt, mid, par
        for (var i = 0; i < tokens.length; i++ ) {
            token = tokens[i]
            next = tokens[i+1]
            each = {
                tok: token,
                //cat: null,
                pos: null,
                //opr: null
                next: next,
                prev: prev,
                unstr: null
            }

            //##if (token == '{' || token == '}') each.cat = 'fin';

            // key
            if (prev == '') {
                each.pos = 'key';
            } else if (prev == '{' && next == '{') {
                each.pos = 'key';

            } else if (prev == '}' && next == '{') {
                each.pos = 'key';
            }

            // brk
            if (token == '}' || token == '{') {
                each.pos = 'brk';
            }

            // par
            if (token == ')' || token == '(') {
                each.pos = 'par';
            }

            // cnd
            if (token.indexOf( '=' ) != -1) {
                each.pos = 'cnd';
            }

            // end
            if (token == ';') {
                each.pos = 'end';
            }

            // left: if prev == {
            if (!each.pos) {
                if (prev == '{' || prev == '}' || prev == ';') {
                    each.pos = 'lft';
                }
            }

            // middle
            if (_isOperator(token)) {
                each.pos = 'mid';
            }

            // right
            if (_isOperator(prev)) {
                each.pos = 'rgt';
            }

            // unstr
            if (each.pos == 'rgt') {
                first = each.tok.charAt(0)
                if ( first == '$' || ( first != '"' && first != "'" ) ) {
                    // make sure it isn't a number
                    if (isNaN(first)) {
                        each.unstr = true
                    }
                }
            }

            prev = each.tok;
            cats.push(each)
        }
    }

    return cats
}

var _isOperator = function(token) {
    result = false;

    if (token) {
        if (token == ':') {
            result = true;
        }
        else if (token.length == 2) {
            for ( var i = 0; i < _preOps.length; i++ ) {
                op = _preOps[i] + ':'
                if (token == op) {
                    result = true
                    break
                }
            }
        }
    }

    return result;
}

var _stringizer = function(cats) {
    var jsonString = ''

    if (cats) {

      var openArray = false
      for (var i = 0; i < cats.length; i++ ) {
        cat = cats[i]

        if (cat.pos == 'brk') {
            jsonString = jsonString + cat.tok

            // add comma?
            if (cat.tok == '}' && cat.next && cat.next != '}') {
                jsonString = jsonString + ','
            }
        } else if (cat.pos == 'key') {
            jsonString = jsonString + '"' + cat.tok + '":'
        } else if (cat.pos == 'end') {
            // found semi colon.  we add no char, or add comma if the next token is not `}`
            if (cat.next.indexOf('=') != -1) jsonString = jsonString + ';'
            else if (cat.next != '}' && cat.next != ')') jsonString = jsonString + ','
        } else if (cat.pos == 'lft') {
            end = (cat.next == '(')? '' : '"' // { or (

            if (cat.unstr)
                jsonString = jsonString + '"`' + cat.tok + '`"'
            else
                jsonString = jsonString + '"' + cat.tok + end
        } else if (cat.pos == 'mid') {
            if (cat.tok == ':') {
                jsonString = jsonString + ':'
            } else {
                jsonString = jsonString + ': ["' + cat.tok.charAt(0) + '",'
                openArray = true
            }
        } else if (cat.pos == 'rgt') {
            if (cat.tok == '""' || cat.tok == "''") {
                jsonString = jsonString + '""'
            } else {

                if (cat.unstr)
                    jsonString = jsonString + '"`' + cat.tok + '`"'
                else if (cat.tok.charAt(0) == "'" || cat.tok.charAt(0) == '"') {
                    // remove both start/end character, force doublequote
                    newToken = '"' + (cat.tok.slice( 1 ).slice(0, -1)) + '"'

                    jsonString = jsonString + newToken
                }
                else
                    jsonString = jsonString + '"' + cat.tok + '"'
            }

            if (openArray) {
                jsonString = jsonString + ']'
                openArray = false
            }
        } else if (cat.tok == ')' && cat.next == '{') {
            jsonString = jsonString + cat.tok + '":'
        } else {
            jsonString = jsonString + cat.tok
        }
      }
      jsonString = "\t{" + jsonString + "}\t"

    }

  return jsonString
}

var $parse = function(raw) {

  pretokens = _tokenize(raw)
  if ($debug) console.log('pretokens', pretokens);
  tokens = _grouper(pretokens)
  if ($debug) console.log('tokens', tokens);
  cats = _categorizer(tokens)
  if ($debug) console.log('cats', cats);

  jsonString = _stringizer(cats)
  if ($debug) console.log('jsonString', jsonString);

  var newObj = JSON.parse(jsonString)

  return newObj
}

module.exports = {
    parse: $parse
};<commit_msg>Update aeon-parser.js<commit_after>var $debug = false;
var _preOps = [ '+', '-', '*', '/', '%', '.', '$', '!' ]; // may be used before colon to form special operator

var _tokenize = function (raw) {

  /** tokenizer **/
  raw = raw.replace(/;/g, "; ")
  raw = raw.replace(/{/g, " {") // why?  because leaving out space in this causes error:  .someclass{
  var tokens = raw.match(/\S+/g)
  if ($debug) console.log({tokens1: tokens})

  // TODO: integrate following block into previous RegExp.match
  // pop the biop (ex: .:, +:, *:) off the end of some tokens, add to new token
  var temp = []
  var len = tokens.length
  for (var i = 0; i < len; i++ ) {
    var tok = tokens[i]
    // if token has a `:`
    if (tok.slice(-1) == ':') {
        withoutColon = tok.slice(0, -1); // remove the ":" from end
        preOp = '';
        withoutOp = withoutColon;

        // is the current last char a preOp char?
        lastChar = withoutColon.slice(-1);
        if (_preOps.indexOf( lastChar ) != -1) {
            // yes it is.  remove it
            preOp = withoutColon.slice(-1);
            withoutOp = withoutColon.slice(0, -1);
        }
        op = preOp + ':';
        temp.push(withoutOp);
        temp.push(op);

    }
    // if the token has `;`
    else if ( tok.indexOf(';') >= 0) {
        mid = tok.indexOf(';')
        temp.push( tok.slice(0, mid) )
        temp.push( ';')

        rgtHalf = tok.slice(mid + 1).trim()
        if (rgtHalf) temp.push(rgtHalf)
    }
    // if the token has `(` or `)`
    else if ( tok.indexOf('(') != -1 || tok.indexOf(')') != -1) {
        // first the `(`
        mid = tok.indexOf('(')
        if (mid != -1) {
            temp.push( tok.slice(0, mid) )
            temp.push('(')
        }

        // next the `)`
        rgtHalf = tok.slice(mid + 1)
        if (rgtHalf.indexOf(')') != -1) {
            mid = rgtHalf.indexOf(')')
            temp.push( rgtHalf.slice(0, mid) )
            temp.push(')')
        }
        else {
            temp.push( rgtHalf )
        }
    }
    else {
        temp.push(tok)
    }
  }
  tokens = temp
  if ($debug) console.log({tokens2: tokens})

  return tokens;
}

/**
 * this fn makes sure that operators are stuck together
 */
var _grouper = function (tokens) {

    groups = []
    combined = ''
    len = tokens.length

    for ( var i = 0; i < len; i++ ) {
        tok = tokens[i]
        if (
            (tok.length == 2 && tok.charAt(1) == ':') ||
            (['{', '}', ':', ';', '(', ')'].indexOf( tok ) != -1)
        ) {
            if (combined) groups.push( combined.trim() )
            combined = ''
            groups.push( tok )
        }
        else {

            combined = combined + " " + tok
        }
    }

    return groups
}

var _categorizer = function(tokens) {
    cats = []

    if (tokens) {
        prev = ''
        next = ''
        // determine key, lft, rgt, mid, par
        for (var i = 0; i < tokens.length; i++ ) {
            token = tokens[i]
            next = tokens[i+1]
            each = {
                tok: token,
                //cat: null,
                pos: null,
                //opr: null
                next: next,
                prev: prev,
                unstr: null
            }

            //##if (token == '{' || token == '}') each.cat = 'fin';

            // key
            if (prev == '') {
                each.pos = 'key';
            } else if (prev == '{' && next == '{') {
                each.pos = 'key';

            } else if (prev == '}' && next == '{') {
                each.pos = 'key';
            }

            // brk
            if (token == '}' || token == '{') {
                each.pos = 'brk';
            }

            // par
            if (token == ')' || token == '(') {
                each.pos = 'par';
            }

            // cnd
            if (token.indexOf( '=' ) != -1) {
                each.pos = 'cnd';
            }

            // end
            if (token == ';') {
                each.pos = 'end';
            }

            // left: if prev == {
            if (!each.pos) {
                if (prev == '{' || prev == '}' || prev == ';') {
                    each.pos = 'lft';
                }
            }

            // middle
            if (_isOperator(token)) {
                each.pos = 'mid';
            }

            // right
            if (_isOperator(prev)) {
                each.pos = 'rgt';
            }

            // unstr
            if (each.pos == 'rgt') {
                first = each.tok.charAt(0)
                if ( first == '$' || ( first != '"' && first != "'" ) ) {
                    // make sure it isn't a number
                    if (isNaN(first)) {
                        each.unstr = true
                    }
                }
            }

            prev = each.tok;
            cats.push(each)
        }
    }

    return cats
}

var _isOperator = function(token) {
    result = false;

    if (token) {
        if (token == ':') {
            result = true;
        }
        else if (token.length == 2) {
            for ( var i = 0; i < _preOps.length; i++ ) {
                op = _preOps[i] + ':'
                if (token == op) {
                    result = true
                    break
                }
            }
        }
    }

    return result;
}

var _stringizer = function(cats) {
    var jsonString = ''

    if (cats) {

      var openArray = false
      for (var i = 0; i < cats.length; i++ ) {
        cat = cats[i]

        if (cat.pos == 'brk') {
            jsonString = jsonString + cat.tok

            // add comma?
            if (cat.tok == '}' && cat.next && cat.next != '}') {
                jsonString = jsonString + ','
            }
        } else if (cat.pos == 'key') {
            jsonString = jsonString + '"' + cat.tok + '":'
        } else if (cat.pos == 'end') {
            // found semi colon.  we add no char, or add comma if the next token is not `}`
            if (cat.next.indexOf('=') != -1) jsonString = jsonString + ';'
            else if (cat.next != '}' && cat.next != ')') jsonString = jsonString + ','
        } else if (cat.pos == 'lft') {
            end = (cat.next == '(')? '' : '"' // { or (

            if (cat.unstr)
                jsonString = jsonString + '"`' + cat.tok + '`"'
            else
                jsonString = jsonString + '"' + cat.tok + end
        } else if (cat.pos == 'mid') {
            if (cat.tok == ':') {
                jsonString = jsonString + ':'
            } else {
                jsonString = jsonString + ': ["' + cat.tok.charAt(0) + '",'
                openArray = true
            }
        } else if (cat.pos == 'rgt') {
            if (cat.tok == '""' || cat.tok == "''") {
                jsonString = jsonString + '""'
            } else {

                if (cat.unstr)
                    jsonString = jsonString + '"`' + cat.tok + '`"'
                else if (cat.tok.charAt(0) == "'" || cat.tok.charAt(0) == '"') {
                    // remove both start/end character, force doublequote
                    newToken = '"' + (cat.tok.slice( 1 ).slice(0, -1)) + '"'

                    jsonString = jsonString + newToken
                }
                else
                    jsonString = jsonString + '"' + cat.tok + '"'
            }

            if (openArray) {
                jsonString = jsonString + ']'
                openArray = false
            }
        } else if (cat.tok == ')' && cat.next == '{') {
            jsonString = jsonString + cat.tok + '":'
        } else {
            jsonString = jsonString + cat.tok
        }
      }
      jsonString = "\t{" + jsonString + "}\t"

    }

  return jsonString
}

var $parse = function(raw) {

  pretokens = _tokenize(raw)
  if ($debug) console.log('pretokens', pretokens);
  tokens = _grouper(pretokens)
  if ($debug) console.log('tokens', tokens);
  cats = _categorizer(tokens)
  if ($debug) console.log('cats', cats);

  jsonString = _stringizer(cats)
  if ($debug) console.log('jsonString', jsonString);

  var newObj = JSON.parse(jsonString)

  return newObj
}

module.exports = {
    parse: $parse
};
<|endoftext|>


<commit_before>
var colLen = 3;

$( document ).ready(function() {
  $.ajax({
    url: 'http://private-3e4b8-xorooms.apiary-mock.com/?token=token',
    type: 'GET',
    dataType: 'json',
    success: function(data, status, xhttp) {
      drawGrid(data);
    },
    error: function(data, status, xhttp) {
      console.log("error");
    }
  });
});

function drawGrid(data) {
  $.each(data.rooms, function( i, obj ) {

    if ((i%colLen) == 0){
      $('#container').append( $('<div/>', {'class':'row'}) );
    }
    $('.row').last().append(
      $('<div/>', {'class':'col'})
      .append( $('<h3/>').append('#'+obj.room) )
      .append( $('<h5/>') ).append('active users: [' + obj["x-user"]+', '+obj["o-user"] + ']')
      );

    console.log("#"+i+" "+obj.room);
  });
}<commit_msg>compatible with api version freeuni-sdp/xo-rooms#9<commit_after>
var colLen = 3;

$( document ).ready(function() {
  $.ajax({
    url: 'http://private-3e4b8-xorooms.apiary-mock.com/?token=token',
    type: 'GET',
    dataType: 'json',
    success: function(data, status, xhttp) {
      drawGrid(data);
    },
    error: function(data, status, xhttp) {
      console.log("error");
    }
  });
});

function drawGrid(data) {
  $.each(data.rooms, function( i, obj ) {

    if ((i%colLen) == 0){
      $('#container').append( $('<div/>', {'class':'row'}) );
    }
    $('.row').last().append(
      $('<div/>', {'class':'col'})
      .append( $('<h3/>').append('#'+obj.room) )
      .append( $('<h5/>') ).append('active users: [' + obj.x_user+', '+obj.o_user + ']')
      );

    console.log("#"+i+" "+obj.room);
  });
}<|endoftext|>


<commit_before>var Stream = require('stream');

// writable
exports.Event = function (stdin) {

    // pipe a handler stream into a source readable stream
    // http request:
    /*
        // call data handlers on data chunk
        req.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        res.write(chunk);
        // (maybe just pass along the res stream?)

    */

    // ws request:
    /*
        // call data handlers on data chunk
        ws.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        ws.write(chunk);
        // (maybe just pass along the ws stream?)

    */

    // flow request:
    /*
        // call data handlers on data chunk
        flow.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        flow.write(chunk);
        // (maybe just pass along the flow stream?)

    */

    // connect a readable source with a handler stream
    // and pass the writable source to the handlers

    // pipe a writable stream
    // source readable (tcp, sensors)
    // source readable in code on('data')
    // writable data in code stream.write(null, data);
    // writable error in code stream.write(err)

    var stdout = Stream.Writable(); // handler stream
    var stderr = Stream.Writable(); // handler stream
    stdin.pipe(stdout);
    stdin.pipe(stderr);

    stdin.on('data', function (chunk) {
        //call data handlers
    })

    stdin.on('error', function (chunk) {
        //call error handlers
    })

    return stdin;
};

// duplex
exports.Handler = function (flow) {

    // create event object with method refs
    // create data\error\end handlers for streams
    var hStream = Stream.Writable();
    hStream.sh = flow.sh;
    hStream.dh = flow.dh;
    hStream.rh = flow.rh;
    hStream.eh = flow.eh;
    hStream.event = function () {
        // call stream handlers
    };

    hStream._write = function (chunk, enc, next) {

        // TODO handle request streams in flow
        //if (hStream.req) {
            // collect all chunks
            // and concat and return data on end
            // use callback as end handler
        //}
        // check if it's an error
        // - call error handlers
        // call all data handlers
    };

    // call all end handlers
    hStream.on('finish', function () {

        // call all end handlers
    });

    /*
    var dh;
    var pos = -1;
    var next = function (err, data) {

        // update handler ref
        dh = this.dh[++pos];

        if (!dh || data === null) {
            // end stream
        }

        if (err) {
            // emit an error (call error handlers)
        }

        dh[1].call(dh[0], stream, dh[2], data, next);
    };

    return function (data) {
        next(null, data);
    };
    */

    console.log(hStream);
    return hStream;
};
<commit_msg>test duplex data push<commit_after>var Stream = require('stream');

// writable
exports.Event = function (stdin, stdout, endHandler) {

    var eventStream = Stream.Duplex();

    eventStream._write = function (chunk, enc, callback) {
        // call all data handlers
        // write data handlers result to writable target stream (stdout, stderr)
        //this.events = {};
        console.log('FLOW EVENT _write:', chunk, enc);
        this.push(chunk);
        callback();
    }

    eventStream._read = function (n) {
        // use data handler result as source
        // this.push(dataHandlerResult);
        console.log('FLOW EVENT _read:', n);
    }

    // pipe a handler stream into a source readable stream
    // http request:
    /*
        // call data handlers on data chunk
        req.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        res.write(chunk);
        // (maybe just pass along the res stream?)

    */

    // ws request:
    /*
        // call data handlers on data chunk
        ws.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        ws.write(chunk);
        // (maybe just pass along the ws stream?)

    */

    // flow request:
    /*
        // call data handlers on data chunk
        flow.on('data', handlerStream.write);

        // write to writable response stream
        stream.write(err, data);
        flow.write(chunk);
        // (maybe just pass along the flow stream?)

    */

    // connect a readable source with a handler stream
    // and pass the writable source to the handlers

    // pipe a writable stream
    // source readable (tcp, sensors)
    // source readable in code on('data')
    // writable data in code stream.write(null, data);
    // writable error in code stream.write(err)

    //var stdout = Stream.Writable(); // handler stream
    //var stderr = Stream.Writable(); // handler stream
    //stdin.pipe(stdout);
    //stdin.pipe(stderr);

    //stdin.on('data', function (chunk) {
        //call data handlers
    //})

    //stdin.on('error', function (chunk) {
        //call error handlers
    //})

    return eventStream;
};

// duplex
exports.Handler = function (flow) {

    // create event object with method refs
    // create data\error\end handlers for streams
    var hStream = Stream.Writable();
    hStream.sh = flow.sh;
    hStream.dh = flow.dh;
    hStream.rh = flow.rh;
    hStream.eh = flow.eh;
    hStream.event = function () {
        // call stream handlers
    };

    hStream._write = function (chunk, enc, next) {

        // TODO handle request streams in flow
        //if (hStream.req) {
            // collect all chunks
            // and concat and return data on end
            // use callback as end handler
        //}
        // check if it's an error
        // - call error handlers
        // call all data handlers
    };

    // call all end handlers
    hStream.on('finish', function () {

        // call all end handlers
    });

    /*
    var dh;
    var pos = -1;
    var next = function (err, data) {

        // update handler ref
        dh = this.dh[++pos];

        if (!dh || data === null) {
            // end stream
        }

        if (err) {
            // emit an error (call error handlers)
        }

        dh[1].call(dh[0], stream, dh[2], data, next);
    };

    return function (data) {
        next(null, data);
    };
    */

    console.log(hStream);
    return hStream;
};
<|endoftext|>


<commit_before>var token = process.env.TOKEN;

var Bot = require('node-telegram-bot-api');
var bot;

if(process.env.NODE_ENV === 'production') {
  bot = new Bot(token);
  bot.setWebHook(process.env.HEROKU_URL + bot.token);
}
else {
  bot = new Bot(token, { polling: true });
}

console.log('Bot server started in the ' + process.env.NODE_ENV + ' mode');

bot.onText(/\/echo (.+)/, (msg, match) {
  var name = msg.from.first_name;
  bot.sendMessage(msg.chat.id, 'Bem vindo ao Bot do grupo RPGzão, ' + name + '!').then(function () {
  });
});

// bot.onText(/^/, function (msg) {
//   var name = msg.from.first_name;
//   bot.sendMessage(msg.chat.id, 'Bem vindo ao Bot do grupo RPGzão, ' + name + '!').then(function () {
//   });
// });

module.exports = bot;
<commit_msg>teste<commit_after>var token = process.env.TOKEN;

var Bot = require('node-telegram-bot-api');
var bot;

if(process.env.NODE_ENV === 'production') {
  bot = new Bot(token);
  bot.setWebHook(process.env.HEROKU_URL + bot.token);
}
else {
  bot = new Bot(token, { polling: true });
}

console.log('Bot server started in the ' + process.env.NODE_ENV + ' mode');

// Matches "/echo [whatever]"
bot.onText(/\/echo (.+)/, (msg, match) => {
  // 'msg' is the received Message from Telegram
  // 'match' is the result of executing the regexp above on the text content
  // of the message

  const chatId = msg.chat.id;
  const resp = match[1]; // the captured "whatever"

  // send back the matched "whatever" to the chat
  bot.sendMessage(chatId, resp);
});

bot.onText(/^/, function (msg) {
  var name = msg.from.first_name;
  bot.sendMessage(msg.chat.id, 'Bem vindo ao Bot do grupo RPGzão, ' + name + '!').then(function () {
  });
});

module.exports = bot;
<|endoftext|>


<commit_before>import React from 'react';
import CompetitionControls from './competition-controls';
import colors from '../colors';

const competitionControlsWrapper = {
  margin: "5px 0 5px 0",
  paddingRight: "1px",
  textAlign: "right",
};

const competitionListWrapper = {
  padding: "5px",
  background: colors.primary2,
  border: `1px solid ${colors.primary1}`,
  borderRadius: "1px",
};

const competitionListRowStyle = {
};

const selectedCompetitionStyle = {
  ...competitionListRowStyle,
  backgroundColor: colors.complementary1,
};

const competitionTableStyle = {
  width: "100%",
  margin: "10px 0 auto 0",
};

const headerStyle = {
  padding: "10px",
  background: "linear-gradient(" + colors.primary1 + ", " + colors.primary3 + ")",
  borderRadius: "4px",
};

const timerDigits = value => ((value < 10) ? "0" : "") + Math.floor(value).toFixed(0);

const timeLeft = competition => {
  // Get creation time
  const start = new Date(competition.createdAt);
  // Work out end time
  const end = new Date(start);
  end.setDate(start.getDate() + (competition.duration / 24));

  const msLeft = end.getTime() - Date.now();
  const secsLeft = msLeft / 1000;
  let minsLeft = secsLeft / 60;
  const hoursLeft = minsLeft / 60;

  // Construct the timer string
  return timerDigits(hoursLeft) + ":" + timerDigits(minsLeft % 60);
};

const CompetitionList = props => {
  const competitions = props.competition.competitions;
  const ids = Object.keys(competitions);
  return (

Competitions

        {ids.length === 0 ?
No competitions running. Create one to get started!
 : null}

              Language
              Time left
              Leader

            {ids.map(id => {
              const competition = competitions[id];
              return (

 props.selectCompetition(id)}
                  key={id}
                  className="competitionListRow"
                  style={props.competition.selected === id
                    ? selectedCompetitionStyle
                    : competitionListRowStyle}>
                  {competition.language}
                  {timeLeft(competition)}
                  {competition.results.length > 0 ?
                    competition.results[0].user.name + ` (${competition.results[0].wpm.toFixed(1)} WPM)` :
                    "-"}

);
            })}

        <CompetitionControls onCreateClicked={props.onCreateClicked} />

);
}

export default CompetitionList;<commit_msg>Don't convert competition id's to strings<commit_after>import React from 'react';
import CompetitionControls from './competition-controls';
import colors from '../colors';

const competitionControlsWrapper = {
  margin: "5px 0 5px 0",
  paddingRight: "1px",
  textAlign: "right",
};

const competitionListWrapper = {
  padding: "5px",
  background: colors.primary2,
  border: `1px solid ${colors.primary1}`,
  borderRadius: "1px",
};

const competitionListRowStyle = {
};

const selectedCompetitionStyle = {
  ...competitionListRowStyle,
  backgroundColor: colors.complementary1,
};

const competitionTableStyle = {
  width: "100%",
  margin: "10px 0 auto 0",
};

const headerStyle = {
  padding: "10px",
  background: "linear-gradient(" + colors.primary1 + ", " + colors.primary3 + ")",
  borderRadius: "4px",
};

const timerDigits = value => ((value < 10) ? "0" : "") + Math.floor(value).toFixed(0);

const timeLeft = competition => {
  // Get creation time
  const start = new Date(competition.createdAt);
  // Work out end time
  const end = new Date(start);
  end.setDate(start.getDate() + (competition.duration / 24));

  const msLeft = end.getTime() - Date.now();
  const secsLeft = msLeft / 1000;
  let minsLeft = secsLeft / 60;
  const hoursLeft = minsLeft / 60;

  // Construct the timer string
  return timerDigits(hoursLeft) + ":" + timerDigits(minsLeft % 60);
};

const CompetitionList = props => {
  const competitions = props.competition.competitions;
  const ids = Object.keys(competitions).map(Number);
  return (

Competitions

        {ids.length === 0 ?
No competitions running. Create one to get started!
 : null}

              Language
              Time left
              Leader

            {ids.map(id => {
              const competition = competitions[id];
              return (

 props.selectCompetition(id)}
                  key={id}
                  className="competitionListRow"
                  style={props.competition.selected === id
                    ? selectedCompetitionStyle
                    : competitionListRowStyle}>
                  {competition.language}
                  {timeLeft(competition)}
                  {competition.results.length > 0 ?
                    competition.results[0].user.name + ` (${competition.results[0].wpm.toFixed(1)} WPM)` :
                    "-"}

);
            })}

        <CompetitionControls onCreateClicked={props.onCreateClicked} />

);
}

export default CompetitionList;<|endoftext|>