|  | @@ -30,8 +30,8 @@ const state = {
 | 
	
		
			
				|  |  |    lastScan: {
 | 
	
		
			
				|  |  |      workers: Date.now() - 100000,
 | 
	
		
			
				|  |  |      ports: Date.now() - 100000,
 | 
	
		
			
				|  |  | -    connections: Date.now() - 100000,
 | 
	
		
			
				|  |  | -  },
 | 
	
		
			
				|  |  | +    connections: Date.now() - 100000
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const typeDefs = `
 | 
	
	
		
			
				|  | @@ -79,7 +79,7 @@ const typeDefs = `
 | 
	
		
			
				|  |  |    type Connection {
 | 
	
		
			
				|  |  |      id: ID!
 | 
	
		
			
				|  |  |      port: Port!
 | 
	
		
			
				|  |  | -    workerProcess: WorkerProcess
 | 
	
		
			
				|  |  | +    workerProcess: WorkerProcess!
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    input ConnectionCommand {
 | 
	
	
		
			
				|  | @@ -90,8 +90,8 @@ const typeDefs = `
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    extend type Query {
 | 
	
		
			
				|  |  |      interfaces: [Interface]!
 | 
	
		
			
				|  |  | -    interface(id: ID, interfaceName: String): Interface!
 | 
	
		
			
				|  |  | -    ports(interfaceName: String, force: Boolean): [Port]!
 | 
	
		
			
				|  |  | +    interface(id: ID!): Interface!
 | 
	
		
			
				|  |  | +    ports: [Port]!
 | 
	
		
			
				|  |  |      port(id: ID!): Port!
 | 
	
		
			
				|  |  |      connections: [Connection]!
 | 
	
		
			
				|  |  |      connection(id: ID!): Connection!
 | 
	
	
		
			
				|  | @@ -108,7 +108,7 @@ const typeDefs = `
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * INTERFACE SECTION
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -async function findInterfaces() {
 | 
	
		
			
				|  |  | +async function findInterfaces () {
 | 
	
		
			
				|  |  |    // 1. Don't check more frequently than once per second.
 | 
	
		
			
				|  |  |    // if (state.lastScan.workers + 1000 > Date.now()) return null
 | 
	
		
			
				|  |  |    // state.lastScan.workers = Date.now()
 | 
	
	
		
			
				|  | @@ -131,8 +131,9 @@ async function findInterfaces() {
 | 
	
		
			
				|  |  |      )
 | 
	
		
			
				|  |  |      if (foundInterface) {
 | 
	
		
			
				|  |  |        // b. If it was modified, save the modification time.
 | 
	
		
			
				|  |  | -      if (foundInterface.workerScript.mtime < mtime)
 | 
	
		
			
				|  |  | +      if (foundInterface.workerScript.mtime < mtime) {
 | 
	
		
			
				|  |  |          foundInterface.workerScript.updated = mtime
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |        return
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      // c. Spawn a new worker connection.
 | 
	
	
		
			
				|  | @@ -145,30 +146,19 @@ async function findInterfaces() {
 | 
	
		
			
				|  |  |        interfaceName,
 | 
	
		
			
				|  |  |        workerScript,
 | 
	
		
			
				|  |  |        workerProcess,
 | 
	
		
			
				|  |  | -      ports: [],
 | 
	
		
			
				|  |  | -      connections: [],
 | 
	
		
			
				|  |  | -      options: await options(workerProcess),
 | 
	
		
			
				|  |  | +      options: await options(workerProcess)
 | 
	
		
			
				|  |  |      })
 | 
	
		
			
				|  |  |    })
 | 
	
		
			
				|  |  |    await Promise.all(workerPromises)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function interfaces(parent, args, context, info) {
 | 
	
		
			
				|  |  | +async function interfaces (parent, args, context, info) {
 | 
	
		
			
				|  |  |    await findInterfaces()
 | 
	
		
			
				|  |  | -  return state.interfaces.map(iface => ({
 | 
	
		
			
				|  |  | -    ...iface,
 | 
	
		
			
				|  |  | -    ports: ports(iface.interfaceName, args, context, info),
 | 
	
		
			
				|  |  | -    // Serialize worker process
 | 
	
		
			
				|  |  | -    workerProcess: workerProcess(iface.workerProcess, args, context, info),
 | 
	
		
			
				|  |  | -  }))
 | 
	
		
			
				|  |  | +  return state.interfaces
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function interface(parent, args, context, info) {
 | 
	
		
			
				|  |  | +async function iface (parent, { id }, context, info) {
 | 
	
		
			
				|  |  |    await findInterfaces()
 | 
	
		
			
				|  |  | -  const { id, interfaceName } = args
 | 
	
		
			
				|  |  | -  if (!id && !interfaceName) {
 | 
	
		
			
				|  |  | -    throw new Error('Either id or interfaceName needs to be provided!')
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  |    const iface = state.interfaces.find(
 | 
	
		
			
				|  |  |      iface => iface.id === id || iface.interfaceName === interfaceName
 | 
	
		
			
				|  |  |    )
 | 
	
	
		
			
				|  | @@ -177,41 +167,41 @@ async function interface(parent, args, context, info) {
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    return {
 | 
	
		
			
				|  |  |      ...iface,
 | 
	
		
			
				|  |  | -    workerProcess: workerProcess(iface.workerProcess, args, context, info),
 | 
	
		
			
				|  |  | +    workerProcess
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function options(workerProcess) {
 | 
	
		
			
				|  |  | +async function options (workerProcess) {
 | 
	
		
			
				|  |  |    const { data, error } = await workerProcess.send({
 | 
	
		
			
				|  |  | -    type: 'options',
 | 
	
		
			
				|  |  | +    type: 'options'
 | 
	
		
			
				|  |  |    })
 | 
	
		
			
				|  |  |    if (error) throw new Error(error)
 | 
	
		
			
				|  |  |    return data
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -function workerProcess(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | +function workerProcess (parent, args, context, info) {
 | 
	
		
			
				|  |  |    const {
 | 
	
		
			
				|  |  |      killed,
 | 
	
		
			
				|  |  |      exitCode,
 | 
	
		
			
				|  |  |      signalCode,
 | 
	
		
			
				|  |  |      spawnargs,
 | 
	
		
			
				|  |  |      spawnfile,
 | 
	
		
			
				|  |  | -    pid,
 | 
	
		
			
				|  |  | -  } = parent.pythonShell
 | 
	
		
			
				|  |  | +    pid
 | 
	
		
			
				|  |  | +  } = parent.workerProcess.pythonShell
 | 
	
		
			
				|  |  |    return {
 | 
	
		
			
				|  |  |      pid,
 | 
	
		
			
				|  |  |      killed,
 | 
	
		
			
				|  |  |      exitCode,
 | 
	
		
			
				|  |  |      signalCode,
 | 
	
		
			
				|  |  |      spawnfile,
 | 
	
		
			
				|  |  | -    spawnargs,
 | 
	
		
			
				|  |  | +    spawnargs
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * PORTS SECTION
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -async function findPorts() {
 | 
	
		
			
				|  |  | +async function findPorts () {
 | 
	
		
			
				|  |  |    // 1. Make sure, workers are updated.
 | 
	
		
			
				|  |  |    await findInterfaces()
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -222,8 +212,8 @@ async function findPorts() {
 | 
	
		
			
				|  |  |    // 3. Loop through all workers to find available ports.
 | 
	
		
			
				|  |  |    const portsPromises = state.interfaces.map(async iface => {
 | 
	
		
			
				|  |  |      // a) Ask interface for ports.
 | 
	
		
			
				|  |  | -    const { data, error, pythonError } = await iface.workerProcess.send({
 | 
	
		
			
				|  |  | -      type: 'ports',
 | 
	
		
			
				|  |  | +    const { data, error } = await iface.workerProcess.send({
 | 
	
		
			
				|  |  | +      type: 'ports'
 | 
	
		
			
				|  |  |      })
 | 
	
		
			
				|  |  |      if (error) throw new Error(error)
 | 
	
		
			
				|  |  |      // b) Add all ports that are not in the list.
 | 
	
	
		
			
				|  | @@ -232,35 +222,31 @@ async function findPorts() {
 | 
	
		
			
				|  |  |        if (state.ports.find(port => port.id === id)) return null
 | 
	
		
			
				|  |  |        const newPort = {
 | 
	
		
			
				|  |  |          id,
 | 
	
		
			
				|  |  | +        interface: iface,
 | 
	
		
			
				|  |  |          interfaceName: iface.interfaceName,
 | 
	
		
			
				|  |  |          host: HOST,
 | 
	
		
			
				|  |  | -        ...port,
 | 
	
		
			
				|  |  | +        ...port
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        state.ports.push(newPort)
 | 
	
		
			
				|  |  | -      iface.ports.push(newPort)
 | 
	
		
			
				|  |  |      })
 | 
	
		
			
				|  |  |    })
 | 
	
		
			
				|  |  |    await Promise.all(portsPromises)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function ports(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | +async function ports (parent, args, context, info) {
 | 
	
		
			
				|  |  |    await findPorts()
 | 
	
		
			
				|  |  | -  const { interfaceName } = args
 | 
	
		
			
				|  |  | -  const ifName = interfaceName || parent
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  if (ifName) {
 | 
	
		
			
				|  |  | -    const iface = state.interfaces.find(iface => iface.interfaceName === ifName)
 | 
	
		
			
				|  |  | -    if (!iface) throw new Error(`Interface ${ifName} not found.`)
 | 
	
		
			
				|  |  | -    return iface.ports
 | 
	
		
			
				|  |  | +  if (parent) {
 | 
	
		
			
				|  |  | +    return state.ports.filter(
 | 
	
		
			
				|  |  | +      port => port.interfaceName === parent.interfaceName
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  |      return state.ports
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function port(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | +async function port (parent, { id }, context, info) {
 | 
	
		
			
				|  |  |    await findPorts()
 | 
	
		
			
				|  |  | -  const { id } = args
 | 
	
		
			
				|  |  | -  if (!id) throw new Error('Need an id.')
 | 
	
		
			
				|  |  |    const port = state.ports.find(port => port.id === id)
 | 
	
		
			
				|  |  |    return port
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -268,102 +254,86 @@ async function port(parent, args, ctx, info) {
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  |   * CONNECTION SECTION
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -async function connect(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | +async function connect (parent, { portId }, ctx, info) {
 | 
	
		
			
				|  |  |    await findPorts()
 | 
	
		
			
				|  |  | -  const { portId } = args
 | 
	
		
			
				|  |  |    const port = state.ports.find(port => port.id === portId)
 | 
	
		
			
				|  |  |    if (!port) throw new Error(`Port ${portId} not found`)
 | 
	
		
			
				|  |  | -  const iface = state.interfaces.find(
 | 
	
		
			
				|  |  | -    iface => iface.interfaceName === port.interfaceName
 | 
	
		
			
				|  |  | -  )
 | 
	
		
			
				|  |  | -  const id = md5(iface.interfaceName + iface.device)
 | 
	
		
			
				|  |  | -  if (iface.connections.find(connection => connection.id === id)) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const id = md5(port.interfaceName + port.device)
 | 
	
		
			
				|  |  | +  if (state.connections.find(connection => connection.id === id)) {
 | 
	
		
			
				|  |  |      throw new Error('already connected.')
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  const pythonWorker = new PythonWorker(iface.workerScript)
 | 
	
		
			
				|  |  | -  const spawnData = await pythonWorker.spawn()
 | 
	
		
			
				|  |  | +  const workerProcess = new PythonWorker(port.interface.workerScript)
 | 
	
		
			
				|  |  | +  const spawnData = await workerProcess.spawn()
 | 
	
		
			
				|  |  |    if (spawnData.error) throw new Error(spawnData.error)
 | 
	
		
			
				|  |  |    const connection = {
 | 
	
		
			
				|  |  |      id,
 | 
	
		
			
				|  |  |      port,
 | 
	
		
			
				|  |  | -    workerProcess: pythonWorker,
 | 
	
		
			
				|  |  | +    workerProcess
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  const connectionData = await connection.workerProcess.send({
 | 
	
		
			
				|  |  | +  const connectionData = await workerProcess.send({
 | 
	
		
			
				|  |  |      type: 'connect',
 | 
	
		
			
				|  |  | -    device: port.device,
 | 
	
		
			
				|  |  | +    device: port.device
 | 
	
		
			
				|  |  |    })
 | 
	
		
			
				|  |  |    if (connectionData.error) throw new Error(connectionData.error)
 | 
	
		
			
				|  |  | -  iface.connections.push(connection)
 | 
	
		
			
				|  |  |    state.connections.push(connection)
 | 
	
		
			
				|  |  | -  return {
 | 
	
		
			
				|  |  | -    ...connection,
 | 
	
		
			
				|  |  | -    workerProcess: workerProcess(pythonWorker),
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +  return connection
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function connections(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | +async function connections (parent, args, ctx, info) {
 | 
	
		
			
				|  |  |    if (parent) {
 | 
	
		
			
				|  |  | -    const iface = state.interfaces.find(iface => iface.interfaceName === parent)
 | 
	
		
			
				|  |  | -    return iface.connections.map(connection => {
 | 
	
		
			
				|  |  | -      return {
 | 
	
		
			
				|  |  | -        ...connection,
 | 
	
		
			
				|  |  | -        workerProcess: workerProcess(connection.workerProcess),
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -    })
 | 
	
		
			
				|  |  | +    return state.connections.filter(
 | 
	
		
			
				|  |  | +      connection => connection.port.interfaceName === parent.interfaceName
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  | -    return state.connections.map(connection => {
 | 
	
		
			
				|  |  | -      return {
 | 
	
		
			
				|  |  | -        ...connection,
 | 
	
		
			
				|  |  | -        workerProcess: workerProcess(connection.workerProcess),
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -    })
 | 
	
		
			
				|  |  | +    return state.connections
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function connection(parent, args, context, info) {
 | 
	
		
			
				|  |  | -  const connection = state.connections.find(
 | 
	
		
			
				|  |  | -    connection => connection.id === args.id
 | 
	
		
			
				|  |  | -  )
 | 
	
		
			
				|  |  | -  return {
 | 
	
		
			
				|  |  | -    ...connection,
 | 
	
		
			
				|  |  | -    workerProcess: workerProcess(connection.workerProcess),
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +async function connection (parent, { id }, context, info) {
 | 
	
		
			
				|  |  | +  const connection = state.connections.find(connection => connection.id === id)
 | 
	
		
			
				|  |  | +  return connection
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function sendCommand(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | -  const { connectionId, command } = args
 | 
	
		
			
				|  |  | +async function sendCommand (parent, { connectionId, command }, ctx, info) {
 | 
	
		
			
				|  |  |    const connection = state.connections.find(
 | 
	
		
			
				|  |  |      connection => connection.id === connectionId
 | 
	
		
			
				|  |  |    )
 | 
	
		
			
				|  |  | +  if (!connection) throw new Error('Connection not found.')
 | 
	
		
			
				|  |  |    const { data, error } = await connection.workerProcess.send({ ...command })
 | 
	
		
			
				|  |  |    if (error) throw new Error(JSON.stringify(error))
 | 
	
		
			
				|  |  | -  return data.response
 | 
	
		
			
				|  |  | +  if (!data.response) {
 | 
	
		
			
				|  |  | +    return JSON.stringify(data)
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    return data.response
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -// TODO Also find connections in interfaces.
 | 
	
		
			
				|  |  | -async function endConnection(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | -  const { connectionId } = args
 | 
	
		
			
				|  |  | +async function endConnection (parent, { connectionId }, ctx, info) {
 | 
	
		
			
				|  |  |    const connectionIndex = state.connections.findIndex(
 | 
	
		
			
				|  |  |      connection => connection.id === connectionId
 | 
	
		
			
				|  |  |    )
 | 
	
		
			
				|  |  | +  if (connectionIndex < 0) throw new Error('Connection not found.')
 | 
	
		
			
				|  |  |    const connection = state.connections[connectionIndex]
 | 
	
		
			
				|  |  | -  const iface = state.interfaces.find(
 | 
	
		
			
				|  |  | -    iface => (iface.interfaceName = connection.interfaceName)
 | 
	
		
			
				|  |  | -  )
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    const { data, error } = await connection.workerProcess.end()
 | 
	
		
			
				|  |  |    if (error) throw new Error(JSON.stringify(error))
 | 
	
		
			
				|  |  | +  if (data) throw new Error(`Process ended with exit code ${data}`)
 | 
	
		
			
				|  |  |    state.connections.splice(connectionIndex, 1)
 | 
	
		
			
				|  |  |    return connection
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -async function killConnection(parent, args, ctx, info) {
 | 
	
		
			
				|  |  | -  const { connectionId } = args
 | 
	
		
			
				|  |  | +async function killConnection (parent, { connectionId, signal = 9 }, ctx, info) {
 | 
	
		
			
				|  |  |    const connectionIndex = state.connections.findIndex(
 | 
	
		
			
				|  |  |      connection => connection.id === connectionId
 | 
	
		
			
				|  |  |    )
 | 
	
		
			
				|  |  | +  if (connectionIndex < 0) throw new Error('Connection not found.')
 | 
	
		
			
				|  |  |    const connection = state.connections[connectionIndex]
 | 
	
		
			
				|  |  | -  const { data, error } = await connection.workerProcess.kill()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  const { data, error } = await connection.workerProcess.kill(signal)
 | 
	
		
			
				|  |  | +  console.log({ data, error })
 | 
	
		
			
				|  |  |    if (error) throw new Error(JSON.stringify(error))
 | 
	
		
			
				|  |  | +  if (data) throw new Error(`Received code ${data}`)
 | 
	
		
			
				|  |  |    state.connections.splice(connectionIndex, 1)
 | 
	
		
			
				|  |  |    return connection
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -371,18 +341,26 @@ async function killConnection(parent, args, ctx, info) {
 | 
	
		
			
				|  |  |  const resolvers = {
 | 
	
		
			
				|  |  |    Query: {
 | 
	
		
			
				|  |  |      interfaces,
 | 
	
		
			
				|  |  | -    interface,
 | 
	
		
			
				|  |  | +    interface: iface,
 | 
	
		
			
				|  |  |      ports,
 | 
	
		
			
				|  |  |      port,
 | 
	
		
			
				|  |  |      connections,
 | 
	
		
			
				|  |  | -    connection,
 | 
	
		
			
				|  |  | +    connection
 | 
	
		
			
				|  |  |    },
 | 
	
		
			
				|  |  |    Mutation: {
 | 
	
		
			
				|  |  |      connect,
 | 
	
		
			
				|  |  |      sendCommand,
 | 
	
		
			
				|  |  |      endConnection,
 | 
	
		
			
				|  |  | -    killConnection,
 | 
	
		
			
				|  |  | +    killConnection
 | 
	
		
			
				|  |  |    },
 | 
	
		
			
				|  |  | +  Interface: {
 | 
	
		
			
				|  |  | +    ports,
 | 
	
		
			
				|  |  | +    connections,
 | 
	
		
			
				|  |  | +    workerProcess
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  Connection: {
 | 
	
		
			
				|  |  | +    workerProcess
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  module.exports = { typeDefs, resolvers }
 |