Skip to content

Commit

Permalink
updated dev instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
awkay committed Dec 22, 2024
1 parent 7efea25 commit a991383
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 150 deletions.
159 changes: 11 additions & 148 deletions DEVELOPMENT.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,173 +9,36 @@

== Architecture

The Chrome and Electron versions of the app share the exact same UI code, but in both systems the UI code must communicate with a separate background system that can accomplish the tasks needed against the running Fulcro app. The FUlcro app could be in a browser, a CLJ runtime, or native app (e.g. mobile). The electron app communicates with the Fulcro app via websockets, whereas the Inspect tool in Chrome uses injected content scripts, a background service worker to communicate with a dev tools panel.
The Chrome and Electron versions of the app share the exact same UI code, but in both systems the UI code must
communicate with a separate background system.
We use fulcro-devtools-remote to abstract this away.

[d2]
-----
Fulcro App <-> Adapter: serialized messages
Adapter <-> Communication Worker: serialized
Communication Worker <-> Dev Tools UI: serialized
Fulcro App <-> Fulcro Devtool Remote
Fulcro Devtool Remote <-> Dev Tools UI: serialized
-----

The Adapter is the inspect preload, which lives in the Fulcro primary repository.
See Fulcro Devtool Remote for more details.

=== Dev Tool Network (remote)
== Building

The dev tool is a Fulcro app. The actual communication from the dev tool happens via mutations and loads, just like any
other Fulcro application. The magic all happens in the custom remote that is created in the tool itself. The remote
is rather simple, it is a local Pathom processor that puts a `send-message` function in the `env`. But, the is slightly complicated
by the fact that the communication itself could be going over a websocket or chrome devtool ports.

Thus, the app/remote has to be created based on IF it is a Chrome plugin, or an electron app. Thus, there is slightly
different code for starting the devtool, in two different namespaces and source folders.

The startup for the chrome tool is in `f.i.c.devtool.main`. The one for Electron is `f.i.electron.renderer.main`.

Because the remotes need to directly access certain details of the embedded environment, the code to handle a remote message
is duplicated. Thus, if you need to support new messages in the tool, you must change both namespaces (see `handle-remote-message`).

=== Chrome vs Websocket Preload

The preloads in Fulcro Inspect basically do the same thing: They start a core.async loop that waits for incoming messages from the Dev Tools UI (through the intermediaries).

The Chrome preload installs a message event listener on the `js/window` (of the app your working on).

In order to ensure that the inspect part of your app doesn't send messages before the devtool is ready, it waits to start the send message processing until it receives a `fulcro-inspect-start-consume` message.

The devtool sends messages identified by `fulcro-inspect-devtool-message`. These are handled by transit decoding the content and the processing the content.
See `handle-devtool-message` in the `inspect-client` ns.

Some messages from devtools are requests to return information (e.g. history steps from the database), and some are requests to run transactions on behalf of the tooling (e.g. a load)

The websocket preload hooks the Fulcro App up to a websocket, and tries to connect it to a running Electron-based dev tool. It uses the exact same transit decoding, and forwarding to `handle-devtools-message`.

The websocket support just avoids sending messages until the websocket itself reports that it is open.

The communications from the Fulcro App are sent into a core async channel (`inspect/send-ch`) that has a dropping buffer of 50k entries. This makes it possible for the app to start and do various things before you ever connect the devtool, but prevents missed messages.

Both preload communication systems read this channel and then forward the messages on to their respective tooling client.

=== Chrome Communication

On startup, the Fulcro Inspect Chrome Extension creates a background service worker, has content scripts that it injects into every web page, and has the devtool pane itself.

==== Background Worker

The background service worker registers for `connect` events with the following two names:

* `fulcro-inspect-devtool` - Communication to/from the devtool UI
* `fulcro-inspect-remote` - Communication to/from the Fulcro App

and ignores any other connect events from the page.

==== Content Script

The Content Script (which is injected onto the web page) looks for an indication the Fulcro app(s) exist on the page. When it detects this, it opens a connection (to the bg worker) and adds a listener that forwards incoming `fulcro-inspect-devtool-message` as a window event, and otherwise tries to determine if the incoming message is a *response* to an outgoing request.

Call/response tracking is done by giving the outgoing request a `__fulcro-insect-message-id` (misspelling is in the code). Such messages are tracked in an atom of `active-messages*` which holds a temporary core.async channel on which the response is expected. The response will include this message id, and can then be put on that temporary channel.

The content script also adds a listener for `fulcro-inspect-remote-message` events, which it places on a core async channel. That channel is used to foward those messages on to the open port (back to the background worker).

.Communication FROM background worker
[d2]
-----
BG -> Content Script : port (type: devtool-message)
Content Script -> Window Event
Window Event -> Inspect Preload: (type: handle-devtool-message)
Inspect Preload -> Fulcro App(s)
-----

.Communication To background worker
[d2]
-----
Fullcro App(s) -> post-message
post-message -> Async Send Channel
Async Send Channel -> Inspect Preload: (async read loop)
Inspect Preload -> Window Event: (type: fulcro-inspect-remote-message)
Window Event -> Content Script
Content Script -> BG: port
-----

The background worker on chrome has a separate port that the devtool opens, and it communicates over that port.

.Background worker to Dev Tool
[d2]
-----
BG <-> Devtool : port
-----

Communication at the DevTool API level is done with a Fulcro Remote that posts messages to that port.

==== Background Script

The background script talks to BOTH the content script on the web page, and the dev tools pane. It keeps track of the open ports (by name), and essentially is just a proxying service.

== Electron

=== Communication

The Electron app has a slightly different set of mechanisms for communication. The Dev Tool code is shared from
the Chrome-based tool, since Chrome is actually used as the rendering engine in Electron. The difference is in how
messages are passed to/from the tool the Fulcro App.

Electron has to use the network to talk to the application, which is running in an entirely different process. We
use websockets for easy bi-directional communication. The security layers in Electron create the same kind of
complexity as in the Chrome plugin, so the architecture is very similar.

Communication from the Fulcro App still goes through a preload, but in this case the preload does not need
an injected content script. Instead, it simply opens a websocket connection to a (hopefully-running) Fulcro Inspect
server (running within Electron).

.Communication FROM Electron App to Fulcro App
[d2]
-----
Devtool -> ipcRenderer
ipcRenderer -> ipcMain: event crosses security boundary (renderer -> server)
ipcMain -> Inspect WS
Inspect WS -> Fulcro App(s): handle-devtool-message
-----

.Communication from Fulcro to Electron
[d2]
-----
Fullcro App(s) -> post-message
post-message -> Async Send Channel
Async Send Channel -> Inspect WS: (async read loop)
Inspect WS -> Electron Server
Electron Server -> ipcRenderer: (.send webContents event)
ipcRenderer -> handle-remote-message
-----

=== Building

Building MacOS releases is done as follows:
Building MacOS Electron releases is done as follows:

----
$ npm install
$ cd shells/electron
$ vi package.json # Update version number
$ yarn
$ cd ../..
$ shadow-cljs release electron-main electron-renderer
$ cd shells/electron
$ electron-builder build -m
----

Building the Windows and Linux releases requires that you have Docker
installed, then you can run a Linux image with a wine-centric builder
via:

----
$ ./build-linux.sh
root@234987:/project# yarn
root@234987:/project# eletron-builder build -wl
root@234987:/project# exit
$ ls dist
----

and the resulting files will be in `dist`.
See the `Makefile` for other builds

= Chrome
== Chrome

First, make sure to update the version number in `shells/chrome/manifest.edn`.

Expand Down
2 changes: 0 additions & 2 deletions shells/electron/build-linux.sh

This file was deleted.

0 comments on commit a991383

Please sign in to comment.