Updated: 2026-09-23 23:31 ET · Audited: pending · Work item: AB#2562
TLDR. Online search embeds Coveo's Atomic web components in the Blazor Hybrid webview.
Atomic needs a short-lived search token that only a backend may mint, because minting needs a
"Search - Impersonate" API key that must never ship in the app. This page lists what Coveo's official
documentation and published npm packages state, with a link for each claim, and lists what they do not cover.
Part 2 (sections 11-16) adds Microsoft, platform and community sources for the webview, the
connectivity probe and the degraded-state UX. Three of its findings change the design: Microsoft advises against
handing tokens to JavaScript, the webview origin cannot be settled from documentation, and no source documents a
Coveo-specific reachability check.
Stated on a Coveo documentation page. The link is in the same row.
Package
Read directly from the published @coveo/atomic@3.61.4 or @coveo/headless@3.57.0 npm package (type definitions, custom-elements.json, source). It is what actually runs, and sometimes differs from the prose docs.
Inferred
Derived from a docs page or the package without an explicit statement. Verify before relying on it.
Scope: sections 1-10 (Part 1) record official Coveo sources only. Sections 11-16 (Part 2) record other sources
and label each row by source type:
MS docs (learn.microsoft.com),
dotnet GitHub (a dotnet/maui issue or source file, dated), GitHub (another repository),
Vendor docs (MDN, Apple, Android, web.dev),
Community (forum, blog or Medium post) and
Inferred.
Neither part contains the app's own design decisions.
1. The big picture
The app asks our backend for a search token. The backend holds a Coveo API key with the "Search - Impersonate" privilege.
The backend calls Coveo's token endpoint and returns the token.
The webview page calls initialize({ accessToken, organizationId, renewAccessToken }) on atomic-search-interface.
Atomic sends queries and analytics events straight to Coveo with Authorization: Bearer <token>.
When the token expires, Coveo answers HTTP 419 and Atomic calls renewAccessToken, which fetches a fresh token from our backend.
Claim
Label
Source
Search tokens are the recommended way to authenticate an interface. They can enforce hub, pipeline and filter, unlike a public API key.
Atomic and Headless renew a token by calling a renewAccessToken callback when the platform returns 419. The callback "returns a new access token, usually by fetching it from a backend service."
POST https://<ORG_ID>.org.coveo.com/rest/search/token?organizationId=<ORG_ID>
Authorization: Bearer <API key with "Search - Impersonate">
Content-Type: application/json
Item
Documented value
Label
Source
Endpoint
POST https://<ORG_ID>.org.coveo.com/rest/search/token. The page says: "To request a search token, make a POST request to https://<ORG_ID>.org.coveo.com/rest/search/token."
name is required (use "anonymous" for an unauthenticated user). provider is required (example: "Email Security Provider"). type is optional: User (default), Group, VirtualGroup or Unknown.
Unresolved. Two points in this table are not settled by Coveo's pages.
(1) One web-search snippet shows the path as /rest/search/v2/token; only /rest/search/token appears on the fetched pages, and the OpenAPI spec at
platform.cloud.coveo.com/docs returned 401 without credentials, so the path was not checked against it.
(2) The docs give a 15-minute minimum for validFor but do not say whether a smaller value is rejected or clamped. Test both against the real organization before coding.
Identity and permissions
Claim
Label
Source
"anonymous" is the documented name for an unauthenticated user.
The identity in userIds must match an identity the source's permissions were indexed against, otherwise permission-trimmed results come back empty.
Inferred
Follows from the identity model on docs.coveo.com/56; not stated as a rule there.
Pipelines route on conditions such as "when $searchHub is X". Enforce searchHub in the token so every query from an interface reaches the right pipeline.
An @source== expression is standard Coveo query syntax. No docs page shows it inside a token filter.
Inferred
No page found.
3. Initializing Atomic
await customElements.whenDefined('atomic-search-interface');
const searchInterface = document.querySelector('atomic-search-interface');
await searchInterface.initialize({
accessToken: token, // from our backend
organizationId: '<ORG_ID>',
renewAccessToken: () => fetchNewToken() // returns a new token string
});
searchInterface.executeFirstSearch();
Claim
Label
Source
The documented usage pattern is whenDefined, then initialize, then executeFirstSearch, with accessToken and organizationId as the required options and renewAccessToken optional.
platformUrl is removed and organizationEndpoints must not be passed. Organization endpoints are now enforced automatically: "Initialize without this property."
Methods: initialize, executeFirstSearch, scrollToTop, updateSearchHub, updatePipeline, toggleAnalytics, updateLanguage, updateIconAssetsPath, getBindings, and the readonly engine and i18n.
The component reference is a JavaScript-rendered Storybook page that could not be read as text:
atomic-search-interface Storybook.
The table above therefore comes from the package, not from that page.
4. Analytics
Claim
Label
Source
Disable analytics with <atomic-search-interface analytics="false"> or el.analytics = false. The package passes the value into the engine's analytics config as enabled, and toggleAnalytics() watches it.
initialize({ analytics: { enabled: false } }) is type-valid. Which of the attribute or this option wins when both are set was not checked.
Inferred
Headless 3.57.0 config types.
Since v3 the documented default analytics mode is Event Protocol (next). "Only Coveo for Commerce supports EP at the moment. For all other implementations, set analyticsMode to legacy."
In 3.61.4 the code falls back to legacy analytics when analyticsMode is unset, which contradicts the docs' "default is next". Set it explicitly and do not rely on either.
Package
Headless 3.57.0 getAnalyticsConfig.
atomic-result-link logs click events. A result template that omits it stops click logging.
Load from the CDN with a pinned version, for example https://static.cloud.coveo.com/atomic/v3.61.3/atomic.esm.js (a type="module" script) and .../themes/coveo.css. Test newer releases before moving the pin in a customer-facing app. Subresource-integrity hashes are published.
Self-hosting: npm i @coveo/atomic@3.61.3. Entry points: @coveo/atomic, /loader, /themes, /assets, /lang. The npm bundle omits on-demand assets, so copy @coveo/atomic/lang/en.json, @coveo/atomic/assets/all.svg and @coveo/atomic/themes/coveo.css into the public directory.
From the CDN URLs on Atomic usage. A vendored build does not need it.
Auth header on queries
Authorization: Bearer <accessToken>
Package
Headless 3.57.0 search request builder.
HIPAA organizations use an orghipaa host and environment: 'hipaa'. The docs also say: "When migrating to organization endpoints, make sure to update your Content Security Policy (CSP)." No host list is published for that CSP.
7. Result templates
Claim
Label
Source
<atomic-result-template><template>...</template></atomic-result-template> must be a direct child of atomic-result-list.
The DNN reference (ALPA.CoveoForDNN, cloned outside this repository) and the removed mobile client (commit 4ef1aa37, removed in 05cf8865) diverge from the documentation in these places.
Item
Reference / removed client
Documentation
validFor
60,000 ms (reference default and removed client)
Minimum 900,000 ms
Token endpoint
Configurable URL in the reference; platform.cloud.coveo.com/rest/search/v2/token in the removed client
<ORG_ID>.org.coveo.com/rest/search/token
Atomic version
atomic/v1 CDN path
Pinned atomic/v3.61.x
Where the key lives
Server-side in the reference; shipped in the app in the removed client
Must stay server-side
Analytics mode
Not set
Set legacy explicitly
Result rendering
search.html has only a search box, no result list
A results page needs atomic-result-list plus a template
11. Microsoft: BlazorWebView, CSP and token hand-off
Conflict with the Coveo token design. Microsoft's Blazor Hybrid security guidance says:
"Avoid passing the tokens to the document context (JavaScript)." Its preferred alternative is to give the
library a temporary token, intercept the outgoing request in native code, swap in the real token, and confirm the
destination is valid. Atomic's renewAccessToken is a JavaScript callback that holds the token. The
Coveo search token is short-lived and permission-scoped, not a long-lived credential, so whether the guidance
applies is a design decision for the team. Sources:
hybrid security,
security considerations.
Topic
Finding
Label
Source
Content host on .NET 10
The default host is now 0.0.0.1; 0.0.0.0 "no longer works" (empty rectangle). The legacy switch is AppContext.SetSwitch("BlazorWebView.AppHostAddressAlways0000", true). The routing page's sample still tests Url.Host != "0.0.0.0", which is stale.
iOS: app://<host>/ (the iOS handler builds AppOrigin that way; WKWebView cannot intercept http/https, so a scheme handler serves local files). Android: https://<host>/. The exact .NET 10 value per platform is not documented.
No Microsoft doc covers it. The WebView enforces CORS. Issue 4003 reports failed preflights for credentialed CORS on iOS while Android worked; no resolution is visible. Whether Access-Control-Allow-Origin: * works from an app:// origin is unconfirmed.
No Microsoft doc says it fails. Microsoft advises avoiding third-party origins where possible and vendoring into wwwroot; if a CDN is unavoidable, use TLS, CSP and Subresource Integrity.
Add <meta http-equiv="Content-Security-Policy" content="..."> just inside <head> of wwwroot/index.html. Meta tags do not support frame-ancestors, report-to, report-uri or sandbox. The sample uses connect-src 'self' http: ws: wss:; Coveo's hosts (section 6) must be added.
A link is internal only when host and scheme match the app origin. Everything else, and target="_blank", opens in the device handler. UrlLoading outcomes: OpenExternally, OpenInWebView, CancelLoad. Microsoft warns against OpenInWebView for external URLs unless fully trusted.
BlazorWebView.WebResourceRequested lets native code rewrite headers per host or answer a request itself (Handled = true plus SetResponse). It runs synchronously on the WebView thread. Whether it sees Coveo's fetch calls on both platforms is not documented.
Import with JS.InvokeAsync<IJSObjectReference>("import", "./x.js") in OnAfterRenderAsync(firstRender) and dispose it. Pass a DotNetObjectReference; JS calls invokeMethodAsync('Name') on a [JSInvokable] method. Dispose the reference or it leaks. A renewAccessToken that calls helper.invokeMethodAsync('GetFreshToken') is assembled from these two pages, not shown by either.
Local static content is served with no-store; the iOS handler sets no-cache, max-age=0, must-revalidate, no-store. StaticContentCacheControlProvider appears only under the .NET MAUI 11 moniker. So on .NET 10 every file of a vendored bundle goes through the managed handler on each load. Fewer, larger files should help (inferred; no bundle-size guidance found).
NetworkAccess.Internet "can only guarantee that a connection is available." The docs' own example is Wi-Fi with a router that has no upstream. Values: Internet, ConstrainedInternet (captive portal), Local, None, Unknown. How the captive portal is detected per platform is not on Learn.
Android 14 with targetSdk 34: a ConnectivityChanged handler threw (RECEIVER_EXPORTED). Also a report that NetworkAccess == Internet did not work. iOS implementation move to NWPathMonitor was planned; status not confirmed. Fix status of these was not checked.
NET_CAPABILITY_VALIDATED means the system probed and found public internet access. A captive-portal network has INTERNET and CAPTIVE_PORTAL but not VALIDATED. Capabilities can change at any time.
Apple probes captive.apple.com/hotspot-detect.html and expects a tiny page containing "Success". Android probes connectivitycheck.gstatic.com/generate_204 and expects an empty 204. Both are OS-internal; an app cannot reuse them as its own check.
Default 100 s; cannot change after the first request (InvalidOperationException). Use a CancellationTokenSource for a per-request limit; the shorter of the two wins. A DNS lookup can take up to 15 s, so a timeout below that may not be honored promptly.
Total timeout 30 s; 3 retries, exponential with jitter, 2 s base; attempt timeout 10 s; circuit breaker at 10% failure ratio, minimum throughput 100, 30 s sampling, 5 s break. Retry can be disabled for unsafe verbs with DisableForUnsafeHttpMethods(). Polly throws TimeoutRejectedException, not TimeoutException. Add only one resilience handler per client.
No Microsoft guidance found. A bounded check is new CancellationTokenSource(TimeSpan.FromSeconds(3)) plus SendAsync(HEAD, ResponseHeadersRead, token), catching OperationCanceledException, on a client without the retry and circuit-breaker pipeline. Built from the Timeout page's advice; not a documented pattern.
Inferred
n/a
Bounded fetch in the page
AbortSignal.timeout(ms) aborts with a TimeoutError and works as fetch(url, {signal}). Baseline since April 2024.
Both the Connectivity page and the platform docs point to the same conclusion: the OS flag is a hint, and only a
real request with a short timeout proves reachability. No source documents a probe against Coveo itself. The
remaining candidate is a first-party endpoint that we own, for example the token-mint endpoint.
13. Platform and web standards: origin, CORS, modules, CSP
Topic
Finding
Label
Source
WKWebView origin
A file:// page sends Origin: null. A custom scheme such as app://localhost sends that scheme. WKURLSchemeHandler serves custom schemes only; it cannot override http or https.
Ordinary CORS: the third party must return Access-Control-Allow-Origin for the page's origin and answer preflight. Third parties often do not allowlist null or app://. Workarounds are a native proxy through the scheme handler or a vendor allowlist. A WebKit change touches CORS for custom schemes registered as CORS-enabled.
WebViewAssetLoader serves assets from https://appassets.androidplatform.net/assets/..., giving a real https origin with normal same-origin and CORS rules. Third parties would have to allow that origin (inferred).
Autonomous custom elements v1 are widely supported. Safari does not support customized built-ins (is=). Minimum iOS and Android WebView versions for Shadow DOM, modules and ES2020: not found.
For own-origin script plus two third-party https hosts: connect-src governs fetch, XHR, WebSocket and EventSource; also script-src, style-src, font-src, img-src and worker-src. MDN confirms the directive names and host-list syntax but not what Atomic needs. Whether Atomic needs 'unsafe-inline' styles or blob workers is inferred. Verify with the console's CSP violation reports.
Defaults are HTTPS, TLS 1.2 or later and forward secrecy. An https third-party host with standard TLS needs no Info.plist change. Whether ATS treats the app:// scheme specially, and how NSAllowsArbitraryLoadsInWebContent applies, is unverified.
Apps targeting API 28 or later default to cleartextTrafficPermitted="false". https to third parties needs no network security config. The manifest needs the INTERNET permission. Android WebView mixed-content behavior was not researched.
Searches found no Coveo-specific WebView, Capacitor, Cordova, CORS or offline material. Only docs.coveo.com pages and one coveo/ui-kit pull request were read; community.coveo.com and StackOverflow returned nothing. Treat "not found" as "not found by these searches".
Topic
Finding
Label
Source
Nonce-based CSP
Atomic's loader exposes setNonce. From a search snippet; the page was not read.
preprocessRequest(request, clientOrigin, metadata) can modify requests before they reach Coveo. The page says nothing about the Origin header. Using it to work around a custom-scheme origin is unverified.
An expired search token gives the Search API a 419 (ExpiredTokenException). The Usage Analytics API gives 400 InvalidToken. renewAccessToken must return a Promise of a token string and is called automatically on 419.
A merged coveo/ui-kit pull request says it renews proactively on expiry and reactively on an unauthorized response, with no process-global state. That is the server-side-rendering path, not the webview case.
Atomic v3 no longer honors doNotTrack (v2 did). The coveo_visitorId cookie is used for tracking. Whether it is still written with analytics off is not stated. Disable before initializing if consent is required.
atomic-query-error handles fatal errors when querying. It links to documentation for a known error and shows the JSON for an unknown one. What it renders when the network is down is unconfirmed. Server errors documented: 429 (back off) and 503 with Retry-After.
Show cached data first and update when the network responds (cache-then-network). Serve the cached copy while fetching an update (stale-while-revalidate). Fall back to a generic "unavailable while offline" state when both fail.
Prefer non-intrusive status messages over modal alerts; keep errors visible while the user fixes them. The Apple HIG guidance was not verified directly; the Nielsen Norman page is general error-message advice, not offline handling.