oddsdk/odd-devtools

Add communication with Webnative

Closed this issue · 1 comments

bgins commented

Summary

Problem

The extension does not communicate with Webnative.

Impact

The extension does not fulfill its primary purpose of debugging Webnative without this feature.

Solution

Implement a communication layer to interact with Webnative.

Detail

Communication methods

The extension can communicate with Webnative by a couple of methods:

  • Call a function added by Webnative to some global scope (e.g. window.navigator)
  • Read data added to some global scope by Webnative
  • Send a window post to Webnative

Webnative can only communicate with the extension using window post messages. We can check that the post messages are from the page where the extension's content script was injected by checking the event.source when receiving a window post message.

Side note: Chrome supports an externally_connectable API that allows a page to send messages directly using the extension runtime. Firefox does not support this feature yet so we can discard it as an option for now.

Connect

We will start the interaction between the extension and Webnative when a developer opens the Webnative devtools panel. The extension will signal to Webnative that it is ready to connect.

We could call a function put onto the global scope by Webnative or send a window post message to Webnative. I think calling a function would be preferable because it is a direct communication between the extension and Webnative. A window post message could be received elsewhere.

Webnative should send a connected window post message to the extension to confirm the connection. We can't directly target the extension with a window post message, but we can tag messages so the extension can ignore any other window post noise.

The connected message could look like:

{
  id: 'webnative-message',
  type: 'connected'
}

We could go further configure Webnative with a unique id sent by the extension when it connects. For example

{
  id: 'webnative-61728e0e-d3ee-4eea-93ff-c4d9a9ab493a',
  type: 'connected'
}

The main benefit here would be to ignore spoofed messages sent by another window poster. Not actually sure if we have a privacy or security concern, so a unique id might be overkill.

Disconnect

We should add a disconnect as well. The extension might disconnect when the developer is not viewing the Webnative devtools panel. Also, Webnative may want to signal to the extension that it has disconnected because something went wrong.

Disconnect could be implemented in roughly the same way as connect. Webnative adds a disconnect function to the global scope that the extension can call. When called, Webnative responds with a disconnect message like:

{
  id: 'webnative-message',
  type: 'disconnected'
}

Data

Data messages are sent from Webnative to the extension. See #2 for a list of data we would like to send.

We will send data messages as window post messages, and we can use a similar format to the other messages with an added data field:

{
  id: 'webnative-message',
  type: 'data',
  data: { 
    // various bits of data 
  }
}

The data that we send must be JSON serializable because the message will pass through extension messaging APIs.

bgins commented

The message protocol has changed to send Webnative state on connect and disconnect messages. In addition, data messages have been have been broken into filesystem and session messages.

Messages

Messages are sent from Webnative to the extension as window post messages. When the extension initially connects, it configures Webnative with an extension ID which is used to tag all messages sent to the extension. The tag lets the extension filter window post messages to only receive messages intended for it.

State

Each message includes some information about the state of Webnative:

type State = {
  app: {
    namespace: AppInfo | string
    capabilities?: Permissions
  }
  fileSystem: {
    dataRootCID: string | null
  }
  user: {
    username: string | null
    accountDID: string | null
    agentDID: string
  }
  odd: {
    version: string
  }
}

Updated on April 21st, 2023.

Connect message

The extension calls a connect function that is added to the global object (e.g. window) by Webnative. When the connect function is called, Webnative sends a connect message to the extension:

globalThis.postMessage({
  id: extensionId,
  type: "connect",
  timestamp: 1678487975,
  state
})

Disconnect message

The extension calls a disconnect function when it does not need messages to be sent from Webnative:

globalThis.postMessage({
  id: extensionId,
  type: "disconnect",
  timestamp: 1678487975,
  state
})

Session messages

Webnative sends session create and destroy messages to the extension. Each message contains a detail field that includes the username and indicates the message type:

globalThis.postMessage({
  id: extensionId,
  type: "session",
  timestamp: 1678487975,
  state,
  detail: {
    type: "create",
    username
  }
})
globalThis.postMessage({
  id: extensionId,
  type: "session",
  timestamp: 1678487975,
  state,
  detail: {
    type: "destroy",
    username
  }
})

File system messages

Webnative sends filesystem local-change and publish messages to the extension. Each message contains a detail field that includes the root CID, path on local-change, and indicates the message type:

globalThis.postMessage({
  id: extensionId,
  type: "filesystem",
  timestamp: 1678487975,
  state,
  detail: {
    type: "local-change",
    root: "<root-cid>",
    path
  }
})
globalThis.postMessage({
  id: extensionId,
  type: "filesystem",
  timestamp: 1678487975,
  state,
  detail: {
    type: "publish",
    root: "<root-cid>"
  }
})