Environments and detection

The same dashlet code runs in all three environments. This page explains how the library decides which one it is in, and why host events cannot be missed even when they arrive before your code is ready.

1. The three environments

Environment Where Wire

webclient

Dashlet in a sandboxed iframe inside the enaio webclient

window.postMessage: onInit / onUpdate events arrive as JSON; method calls are posted as [method, params, { msgId }] and correlated by the msgId in the host’s { type: "response", … } reply

richClient

Dashlet in the embedded WebView2 of the Windows rich client

Injected window.osClient (synchronous osjx* bridge) plus the host-called global osDashletInit(data); there is no initial event, so the library self-initializes from the bridge

standalone

Plain tab, dev server, or any non-enaio embed

No bridge, no events; the bounded degraded state (fallback, never a hang)

In every environment the init promise resolves to the same normalized InitContext — the environment only changes which wire is live.

2. How the library decides: the detection state machine

start -> detecting
           |  bridge found (immediate probe or poll tick)
           +-----> rich client self-init (bridge read) -> initialized
           |  webclient onInit accepted (origin OK, first event)
           +-----> webclient init -> initialized
           |  detection cap elapsed (no bridge, no event)
           +-----> standalone (observable degradation, not an error)

Settling rules:

  • The state machine settles exactly once; init resolves exactly once and never rejects — the standalone branch is an observable fallback, not an error.

  • Rich client: the bridge may be injected late, so detection polls window.osClient (pollIntervalMs, default 50 ms) until it appears or the detection cap (detectionTimeoutMs, default 5000 ms) elapses. Bridge capability probes are throw-safe: depending on the client version, reading an unknown member either throws or returns undefined — both mean "not present". The bridge itself may be callable: the WebView2 rich client (measured on 12.0.713) injects a host-object proxy around a function, so typeof window.osClient is 'function'; the probes accept an object bridge and a callable bridge alike. Once the bridge read succeeds, the rich-client initialization proceeds with zero host calls.

  • Webclient: the first onInit accepted by origin validation settles the state (see Origin configuration). After the first webclient event, the client type declared by the host is definitive — a client type of rich_client reroutes resolution to the rich-client path.

  • Standalone: no bridge and no accepted event within the cap — the fallback state, documented in Standalone operation.

3. Import-time self-registration: never miss a host event

Registration of the host interface does not depend on your framework or on user code calling installDashletGlobal "soon enough". Importing the package already does the following synchronously at module-evaluation time:

  • installs window.osDashletInit immediately — the rich client fires its first call without waiting for load;

  • attaches the single buffering message listener — the only message listener a dashlet window ever needs.

Host calls and postMessage events arriving before installDashletGlobal runs are buffered — bounded, in arrival order — and flushed when the dashlet is configured; they are never dropped. Registration is idempotent: the installed global is a stable per-window dispatcher, and re-registration adds or removes handlers atomically without losing a call that arrives in between.

Additionally, an onInit listener registered after the init already settled (for example by a framework after mount) is replayed immediately with the settled context — late registration never misses the init.

4. The v10.10 warm-up quirk

On client version 10.10, osjxGetEnvironment(n) returns empty values until the client has finished warming up. If the self-init bridge read yields no environment values at all, the library runs a bounded warm-up (the poll interval, capped by detectionTimeoutMs plus an internal 10 s hard cap) and refreshes + re-emits the populated context via the update slot once the values appear — never a hang, and never a silent overwrite of values that were already read.