Setup
Declare alive block on your integration entry:
Egress validation
The harness enforces your definition’segress allowlist in live mode. A browser request to an undeclared origin throws EgressDeniedError (from @ekanos/sdk/context, code: 'EGRESS_DENIED'):
redirect: 'error', so a redirect to another origin fails rather than being followed.
This is a harness convenience, not a preview of production enforcement: production runs no egress check on the client path today. Flipping to live mode is still a real test of the declaration under review, not of a second list kept beside it.
Credential storage
Live mode reads admin-tier credentials (the OAuth client id and secret, and signed webhooks’ secrets) from.ekanos/local-secrets.json in your project root (gitignored, never packed by ekanos publish, read on every request). The harness generates the file on first use. OAuth tokens are not written to it.
On the Activation or Triggers surface:
- Find the Local OAuth panel (shown for integrations that declare
oauth, in either mode) - Follow the checklist to create a developer app at your vendor’s console
- Paste the client id, secret, and any webhook signing secrets into the form (one field per secret name your definition declares)
- Click Save credentials
.ekanos/local-secrets.json and never echoes values back. A saved key shows as filled beside an empty field; a blank field keeps what is saved. Saving, or editing a value in the file, rebuilds the live context and forgets any current connection.
The OAuth connection (tokens) lives in dev-server memory only: restarting the
dev server forgets it and you Connect again. Activation data submitted through
a plain activation form is also held in memory only, never written to
localStorage, a fixture, or the registry, and is cleared on reload and on
Disconnect.Opting in
An integration with nolive block is fixtures-only; the toolbar control is disabled for it and says why. The harness also forces fixtures mode while a live.egress is wider than the definition’s egress.
One WITH a live block opens in fixtures mode by default. To use live mode:
- Select Live API in the toolbar’s Data dropdown, or
- Declare
live: { default: 'live' }— for integrations whose widgets never call the vendor from the browser (every vendor call is server-side behindctx.fetch)
live.default and is saved in localStorage, so it survives reloads.
Keys that stay mocked (seeds)
live.seeds lists the react-query keys that stay mocked in live mode — Ekanos-side state your widgets read before they can call anything (a saved setting, an account preference). Everything not listed runs its real query function:
- Mocked: Ekanos-side state
- Live: your vendor
OAuth loop on localhost
For an integration withoauth declared:
1
Switch to Live API (the entry needs a
live block).2
On the Activation or Triggers surface, find the Local OAuth panel.
3
Follow the checklist to set up a developer app at your vendor’s console. It is derived from your declaration: the redirect URI
http://localhost:3100/api/integrations/<slug>/oauth/callback on the default port (Copy button), the declared scopes, and the code grant (with PKCE when declared). Add oauth.setup to the entry for the console link and key names it cannot know.4
Paste the client id, secret, and any webhook signing secrets into the form and
click Save credentials.
5
Click Connect in the activation dialog. The shell’s
oauth/connect route
builds the authorize URL (PKCE when declared) and sends the browser to your
vendor.6
Your vendor redirects back to
oauth/callback, which verifies the single-use
state, exchanges the code through ctx.fetch (an undeclared token-endpoint
origin fails with EgressDeniedError), and awaits your onTokens handler on
the live context.7
oauth/status then answers connected, the SDK’s OAuthActivationForm
auto-activates, and onActivate runs on the same context.8
Your widgets read what the handlers wrote through
fetchIntegrationStorage(), unchanged. An empty read runs your declared schedules once first, as the host does.Host is not loopback unless EKANOS_HARNESS_EXPOSED=1; ekanos dev --host sets that after printing a warning.
API routes available
The shell’s API route (createHarnessApiHandlers from @ekanos/harness/api) serves these under /api/integrations/<slug>/:
In live mode the browser passes the active integration’s requests to these routes.
local-setup and actions/<actionName> are reached in both modes. The routes run against one server-held context per integration, whose ctx.fetch reaches your vendor under the declared egress.
Widget data fetching
UseuseIntegrationFetch() from @ekanos/sdk/hooks. The harness mounts IntegrationFetchProvider and IntegrationActivationDataProvider unconditionally, so the hook returns a fetch already gated for whichever mode the toolbar is in, and useActivationData() returns what was last submitted to the activation form (null before any submission or after Disconnect):
fetch is answered from your fixtures. In live mode, it reaches your real API.
Taking an
IntegrationFetch as an argument lets the same query function serve
ctx.fetch on the server and this hook’s fetch on the client. In the harness
the client fetch is gated by the harness’s own check; in production today it
is a plain, unmediated browser fetch.live.FetchProvider field is deprecated. It still works and receives fetch, mode, and activationData as props; use it only to compose an authenticated fetch once at a single seam.
Knowing which mode is active
CalluseHarnessDataMode() from @ekanos/harness/hooks. It has no SDK equivalent, so call it only from code that runs nowhere but the harness:
live block or a live.egress/definition mismatch (both force fixtures regardless of the toolbar).
Troubleshooting
EgressDeniedError in live mode
The origin is not in your definition’segress. Add it or fix a typo in your URL.
NoRecordedResponseError in live mode
The request was a same-origin/api/… request outside the active integration’s own /api/integrations/<slug>/… routes. Those are answered from fixtures in both modes; record a fixture for it, or call your vendor with an absolute URL.
Can’t find where credentials were saved
Check.ekanos/local-secrets.json in your project root. The file is gitignored and never committed.
Changing credentials
Live connections are memory-only and forgotten on restart. To change credentials, save new values in the Local OAuth panel or edit.ekanos/local-secrets.json; either forgets the current connection.