Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC API library #27

Closed
aesedepece opened this issue May 14, 2018 · 6 comments
Closed

IPC API library #27

aesedepece opened this issue May 14, 2018 · 6 comments
Assignees
Labels
backend 📦 Requires code to be executed in a V8 context

Comments

@aesedepece
Copy link
Member

The IPC API library is an IPC server that wraps and exposes backend methods to the frontend.

@aesedepece aesedepece added backend 📦 Requires code to be executed in a V8 context Epic labels May 14, 2018
@aesedepece
Copy link
Member Author

@kronolynx 's research on IPC is in #23

@aesedepece aesedepece added this to the Sheikah Sprint #2 milestone May 22, 2018
@aesedepece
Copy link
Member Author

aesedepece commented May 23, 2018

Whoever gets assigned this story will likely need to:

  1. Play with @kronolynx snippet in Assess backend <> frontend communication solutions for Electron #23 to master how IPC works in Electron and how to use it both in the main process (NodeJS) and the renderer process (web context).
  2. Find the right place in the project directories for both sides of the IPC API.
  3. Implement a ping/pong method in the IPC API.
  4. Assess if testing IPC makes sense at all. If it does, guess how to and write tests for (3).
  5. Continue with Expose wallet creation methods #28 and Expose wallet unlocking methods #29 once Keypair and wallet generation (backend only) #19 and Basic wallet functionality (backend only) #21 maturate (after Crypto module #31 gets merged)

@aesedepece
Copy link
Member Author

This is being worked by @mmartinbar in her ipc branch.

@aesedepece
Copy link
Member Author

As per latest design, API methods are implemented in the backend by exporting them inside the /handlers folder, which shall be eventually renamed to /api.

Those methods need to comply with the ChanDesc type.

mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 7, 2018
Common functionality for back and front ends given by IPCCommon module. Dummy handlers for a
ping-pong basic test. Implemented directory-based IPC handler loading logic.

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 7, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
@aesedepece
Copy link
Member Author

aesedepece commented Jun 10, 2018

Synchronous model

Main process implementation

ipcMain.on(IPC_SYNC_CHAN_NAME, async (event: any, args: any) => {
  // Reconstruct request from serialized message
  const syncRequest: ChanRequest = JSON.parse(args)
  // Check request validity
  if (!isValidChanRequest(syncRequest)) throw new Error("Malformed IPC request")
  // Check if method is supported
  if (syncRequest.method in methods) {
    // Call matching method handler to get specific response
    const responseMessage = await channels[chanReq.method](chanReq.params)
    // Wrap response into a generic protocol response
    const response = buildChanResponse(ChanResCode.Ok, chanReq.id, responseMessage)
  } else {
    // Wrap error message into a generic protocol response
    const response = buildChanResponse(ChanResCode.ErrUnknownMethod, chanReq.id, `Unsupported method: ${syncRequest.method}`)
  }
  // Return the wrapped response
  event.returnValue = response
})

Renderer process implementation

None

Renderer process usage

// Construct a new request
const syncRequest = buildChanRequest("ping")
// Send request and wait for response
const response = ipcRenderer.sendSync(IPC_SYNC_CHAN_NAME, syncRequest)

Asynchronous model

Main process implementation

ipcMain.on(IPC_ASYNC_CHAN_NAME, async (event: any, args: any) => {
  // Reconstruct request from serialized message
  const syncRequest: ChanRequest = JSON.parse(args)
  // Check request validity
  if (!isValidChanRequest(syncRequest)) throw new Error("Malformed IPC request")
  // Check if method is supported
  if (syncRequest.method in methods) {
    // Call matching method handler to get specific response
    const responseMessage = await channels[chanReq.method](chanReq.params)
    // Wrap response into a generic protocol response
    const response = buildChanResponse(ChanResCode.Ok, chanReq.id, responseMessage)
  } else {
    // Wrap error message into a generic protocol response
    const response = buildChanResponse(ChanResCode.ErrUnknownMethod, chanReq.id, `Unsupported method: ${syncRequest.method}`)
  }
  // Send the wrapped response back
  sendAsyncMessage(event.sender, JSON.stringify(response))
})

Renderer process implementation

// Holds promises for each pending request
const pendingRequests = {}

// Keeps a count of sent requests
let requestsCount = 0

// Creates new requests
const buildChanRequest = (method: string, params?: any): ChanRequest => {
  return { id: requestsCount++, method, params }
}

// Conviniently wraps all the IPC and request/response correlation magic
const asyncSend = async (asyncRequest: ChanRequest, timeout = 5000): ChanResponse => {
  // Create a promise for each message
  const promise = new Promise((resolve, reject) => {
    // Create the timeout and get a reference to the timer
    const timer = setTimeout(() => {
      const error = new Error(`Timed out after ${timeout}ms`)
      error.name = "TimeoutError"
      reject(error)
    }, timeout)
    // Store the resolver, rejecter and timer references
    pendingRequests[asyncRequest.id] = [resolve, reject, timer]
  })
  // Send the asynchronous request
  ipcRenderer.send(IPC_ASYNC_CHAN_NAME, JSON.stringify(asyncRequest))
  // Return the promise
  return promise
}

// Only response entry point in the renderer process
ipcRenderer.on(IPC_ASYNC_CHAN_NAME, async (event: any, args: any) => {
  // Reconstruct request from serialized message
  const asyncResponse: ChanResponse = JSON.parse(args)
  // Check response validity
  if (!isValidChanResponse(asyncResponse)) throw new Error("Malformed IPC response")
  // Find request for which this is a response 
  if (asyncResponse.id in pendingRequests) {
    const [resolve, reject, timer] = pendingRequests[asyncResponse.id]
    // As we got a response, let's unset the timeout
    clearTimeout(timer)
    // Resolve or reject depending on success or error
    if (asyncResponse.error) {
      reject(asyncResponse.error)
    } else {
      resolve(asyncResponse.result)
    }
  } else {
    // Ignore it? This is probably a late reponse (arrived after timing out)
  }
})

Renderer process usage

Naive approach
const asyncRequest: ChanRequest = buildChanRequest("ping")
const asyncResponse = await asyncSend(asyncRequest)

Responsible approach

const asyncRequest: ChanRequest = buildChanRequest("ping")
asyncSend(asyncRequest)
  .then((response) => {
    console.log("Backend responded", response)
  })
  .catch((error) => {
    if (error.name === "TimeoutError") {
      console.error("Backend timed out", error)
    } else {
      console.error(error)
    }
  })

Using then/catch allows the renderer process to update the UI according to whatever happens with the request.

mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 11, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 11, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 11, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
@aesedepece
Copy link
Member Author

Today we had an informal yet very interesting discussion on how to test our IPC API.

These were our conclusions:

  • We SHALL NOT create integration or functional tests with the real Electron IPC in place. It's overkill.
  • We MUST abstract/detach renderer API methods from the actual IPC calls. This gives us the chance to substitute ipcRenderer with a mock transport and unit-test if these methods construct the messages as expected.
  • We MUST abstract/detach main API handlers from the actual IPC calls. This gives us the chance to substitute ipcMain with a mock transport and unit-test if the handlers construct the responses as expected.
  • We MAY create integration tests covering the whole lifetime of a renderer<>main request/response. This is done by using a pass-through transport directly connecting the renderer to the main process. Yes, this can be done in the tests only because we are cheating: we're running browser code (the renderer API methods) in a NodeJS context 😛.

mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
A cleaner functional approach to get the map of method handlers

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
Basic unit testing for all functions inside IPC common library

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
A cleaner functional approach to get the map of method handlers

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 13, 2018
Basic unit testing for all functions inside IPC common library

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 14, 2018
IPC dependencies decoupled from front-end and back-end API libraries (though default communication
is IPC). This should ease the unit testing of the libraries.

witnet#27
mmartinbar pushed a commit to mmartinbar/sheikah that referenced this issue Jun 14, 2018
Added unit tests for some front-end library functions.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
IPC dependencies decoupled from front-end and back-end API libraries (though default communication
is IPC). This should ease the unit testing of the libraries.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Added unit tests for some front-end library functions.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Changes in code structure, better directory organisation, some name changes suggested by the team.
Added extra checks in channel msg handling as well. Exported handlers can now be sync as well.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
A cleaner functional approach to get the map of method handlers

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Basic unit testing for all functions inside IPC common library

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
IPC dependencies decoupled from front-end and back-end API libraries (though default communication
is IPC). This should ease the unit testing of the libraries.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Added unit tests for some front-end library functions.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Changes in code structure, better directory organisation, some name changes suggested by the team.
Added extra checks in channel msg handling as well. Exported handlers can now be sync as well.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 21, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
A cleaner functional approach to get the map of method handlers

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Basic unit testing for all functions inside IPC common library

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
IPC dependencies decoupled from front-end and back-end API libraries (though default communication
is IPC). This should ease the unit testing of the libraries.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Added unit tests for some front-end library functions.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Changes in code structure, better directory organisation, some name changes suggested by the team.
Added extra checks in channel msg handling as well. Exported handlers can now be sync as well.

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Well-formatted ts file was not committed in previous commit

witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Implementation of functionality to send and receive async messages from front-end, with timeout
handling. A bit of refactor as well.

wip witnet#27
anler pushed a commit to anler/sheikah that referenced this issue Jun 25, 2018
Adapted IPC work to project layout defined in witnet#126

witnet#27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend 📦 Requires code to be executed in a V8 context
Projects
None yet
Development

No branches or pull requests

2 participants