Home Reference Source

lib/contexts/Context.js

const uuid = require('uuid')
const request = require('request-promise')

class Context {
  constructor (host, name) {
    this._host = host
    this._id = `node-${this.constructor.name.toLowerCase()}-${uuid()}`
    this._name = name
    this._variables = {}
  }

  get host () {
    return this._host
  }

  get id () {
    return this._id
  }

  get name () {
    return this._name
  }

  get location () {
    return {
      context: {
        id: this._id,
        name: this._name
      },
      host: {
        id: this._host.id,
        port: this._host.servers.http && this._host.servers.http.port
      },
      machine: {
        id: this._host.machine.id,
        ip: this._host.machine.ip
      }
    }
  }

  async libraries () {
    return {}
  }

  async pack (value) {
    return this.packPackage(value)
  }

  /**
   * Unpack a data node into a native data value
   *
   * @param  {Object} node A data node (either a data packet or data pointer)
   * @return {[type]}      [description]
   */
  async unpack (node) {
    return this.unpackPackage(node)
  }

  async packPackage (value) {
    let type
    if (value === null) type = 'null'
    else type = value.type || typeof value
    switch (type) {
      case 'image':
        return {type, src: value.src}
      default:
        return {type, data: value}
    }
  }

  async unpackPackage (pkg) {
    const type = pkg.type
    switch (type) {
      default: return pkg.data
    }
  }

  async packPointer ({type, name, preview = null}) {
    let {context, host, machine} = this.location
    return {
      type,
      path: {
        value: {
          id: 'value-' + uuid(),
          name: name
        },
        context,
        host,
        machine
      },
      preview: preview
    }
  }

  async unpackPointer (pointer) {
    const path = pointer.path
    const what = {
      name: path.value.name
    }
    if (path.context.id === this.id) {
      // Get data from this context
      return this.resolve(what)
    } else if (path.host.id === this.host.id) {
      // Get a context on this host to provide data
      let context = await this.host.resolve(path.context.name)
      let packag = await context.provide(what)
      return this.unpack(packag)
    } else if (path.machine.id === this.host.machine.id) {
      // Make a HTTP request to context via another host on this machine
      return this.host.generateToken(path.host.id).then(token => {
        const url = `http://127.0.0.1:${path.host.port}/${path.context.name}!provide`
        const auth = 'Bearer ' + token
        return request({
          method: 'PUT',
          uri: url,
          headers: {
            Accept: 'application/json',
            Authorization: auth
          },
          body: what,
          json: true
        })
      }).then(packag => {
        return this.unpack(packag)
      })
    } else {
      // Currently, because access keys are not exchanged between machines, this
      // is not possible. It may be possible in the future.
      throw new Error('Inter-machine pointers are not yet supported')
    }
  }

  async resolve (what) {
    if (!what.name) throw new Error('No name in: ' + what)
    const value = this._variables[what.name]
    if (value === undefined) throw new Error('No such value: ' + what.name)
    return value
  }

  async provide (what) {
    const data = await this.resolve(what)
    return this.pack(data)
  }

  async compile (cell) {
    let source
    if (typeof cell === 'string' || cell instanceof String) {
      source = cell
    } else if (typeof cell === 'function') {
      source = cell.toString()
    } else {
      source = cell.source.data
    }

    return {
      source: {
        type: 'string',
        data: source
      },
      expr: cell.expr || false,
      global: cell.global || false,
      options: {},
      inputs: [],
      outputs: [],
      messages: []
    }
  }

  async execute (cell) {
    return cell
  }

  async evaluate (node) {
    switch (node.type) {
      case 'get': return this.evaluateGet(node)
      case 'call': return this.evaluateCall(node)
      default: return this.unpack(node)
    }
  }
}

Context.spec = {
  name: 'Context',
  client: 'ContextHttpClient'
}

module.exports = Context