Spyglass Architecture
Spyglass is split into two major parts: the Spyglass core, and a set of independent lenses. Lenses are designed to run statelessly and without any knowledge of the world outside being provided with a list of artifacts. The core is responsible for selecting lenses and providing them with artifacts.
Spyglass Core
The Spyglass Core is split across pkg/spyglass
and cmd/deck
. It has
the following responsibilities:
- Looking up artifacts for a given job and mapping those to lenses
- Generating a page that loads the required lenses
- Framing lenses with their boilerplate
- Facilitating communication between the lens frontends and backends
Spyglass Lenses
Spyglass Lenses currently all live in pkg/spyglass/lenses
, though hopefully in the
future they can live elsewhere. Spyglass lenses have the following responsibilities:
- Fetching artifacts
- Rendering HTML for human consumption
Lens frontends are run in sandboxed iframes (currently sandbox="allow-scripts allow-top-navigation allow-popups allow-same-origin"
), which ensures that they can only interact with the world via the
intended API. In particular, this prevents lenses from interacting with other Deck pseudo-APIs or with
the core spyglass page.
In order to provide this API to lenses, a library
(cmd/deck/static/spyglass/lens.ts
) is injected into
the lenses under the spyglass
namespace. This library communicates with the spyglass core via
window.postMessage
. The
spyglass core then takes the requested action on the lens’s behalf, which includes facilitating
communication between the lens frontend and backend. The messages exchanged between the core and the
lens are described in cmd/deck/static/spyglass/common.ts
.
The messages are exchanged over a simple JSON-encoded protocol where each message sent from the lens
has an ID number attached, and a response with the same ID number is expected to be received.
For the purposes of static typing, the lens library is ambiently declared in
pkg/spyglass/lenses/lens.d.ts
, which just re-exports the definition of
spyglass
from lens.ts
.
This design is discussed in its implementation PR.
Lens endpoints
Lenses are exposed by the spyglass core on the following Deck endpoints:
URL | Method | Purpose |
---|---|---|
/spyglass/lens/:lens_name/iframe |
GET | The iframe view loaded directly by the spyglass core |
/spyglass/lens/:lens_name/rerender |
POST | Returns the lens body , used by calls to spyglass.updatePage and spyglass.requestPage |
/spyglass/lens/:lens_name/callback |
POST | Allows the lens frontend to exchange arbitrary strings with the lens backend. Used by spyglass.request() |
In all cases, the endpoint expects a JSON blob via the query parameter req
that contains
bookkeeping information required by the spyglass core - the artifacts required, what job this is
about, a reference to the lens configuration. This information is attached to requests by the
spyglass core, and the lenses are not directly aware of it. In the case of the POSTed endpoints
/rerender
and /callback
, the lens can choose to attach an arbitrary string for its own use. This
string is passed through the core as an opaque string.
Some additional query parameters are attached to the iframes created by the spyglass core. These are not used by the backend, and are provided as a convenient means to synchronously provide information from the frontend core to the frontend lens library.
Page loading sequence
When a spyglass page is loaded, the following occurs:
- The core backend generates a list of artifacts for the job (e.g. by listing from GCS)
- The core backend matches the artifact list against the configured lenses and determines which ones to display.
- The core backend generates an HTML page with the lens->resource mapping embedded in it as JavaScript objects.
- The core frontend reads the embedded mapping and generates iframes for each lens
- The core receives the simultaneous requests to the lens endpoints and invokes the lenses to generate their content, injecting the lens library alongside some basic styling.
After this final step completes, the page is fully rendered. Lenses may choose to request additional information from their frontend, in which case the following happens:
- The lens frontend makes a request to the core frontend
- The core frontend attaches some lens-specific metadata and makes an HTTP request to the relevant lens endpoint
- The core backend receives the request and invokes the lens backend with the relevant information attached.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.