
Connect browser services without exposing server secrets
Browser providers carry only the public settings and code a page needs, never a server secret. Wildo’s browser modules cover error monitoring, analytics, spam protection on forms, file picking from Google Drive, OneDrive or Dropbox, and address lookup, each turned on per surface.
Their declared network origins contribute to the surface’s content security policy. Their activation code remains separate from the data sent by the server.
Sign-in and payment have no browser module: signing in with Google, Microsoft, GitHub or Apple, and paying through Stripe, are redirects to the vendor, so no vendor code runs in your page; the page only shows the vendor’s name from the server’s configuration.
Example — Enable monitoring on one surface
A website selects an error-monitoring provider and supplies its public endpoint. That endpoint informs both SDK activation and the policy allowing the browser to connect to it.
For engineers
Frontend providers travel through two channels. Served entries contain identity, public configuration and descriptors; a code map generated for each frontend service supplies the actual module functions. hydrateFrontendProviderModules joins them by provider identity. Data without matching code is diagnosed; code for an unselected provider stays inactive.
The application declares a provider on the intended frontend service and supplies its public configuration. wildo config sync then writes both channels: the served data, and the code map the service passes to its startup. A registry provider module brings its browser SDK as its own dependency. Selecting a provider does not supply a real account configuration.
Supply public data and browser code at their separate homes
Wonder CRM configures its product to report browser errors to a self-hosted Sentry. It selects sentry for its app frontend service with FRONTEND_ERROR_MONITORING and FRONTEND_SDK, and supplies the public DSN per environment, in the environment’s infrastructure file rather than in the provider selection block:
The block takes this shape in wildo.infra.<env>.config.ts, with the DSN the operator’s Sentry issues. Wonder CRM ships none: until an operator supplies one, wildo config sync and wildo local doctor report Sentry as declared but not configured, naming the missing dsn, and the browser does not start it.
providerConfigurations: {
sentry: {
[ExternalProvider_ExchangeProtocol_Kind.FRONTEND_SDK]: {
dsn: 'https://<public-key>@<your-sentry-host>/<project-id>',
},
},
},
ExternalProvider_ExchangeProtocol_Kind comes from @wildo-ai/external-connectors-models. A DSN is public by construction, which is why it sits in the public FRONTEND_SDK configuration rather than in a secret. Synchronization overlays this setting onto the contribution’s public configuration while retaining its SDK and CSP descriptors. It rejects keys outside a declared public-key contract and keys identified as secrets by the same provider’s server contributions.
That is the served data. The executable half is generated by the same synchronization: a code map in src/generated/frontend-providers.generated.ts for each frontend service, importing every provider the service declares from the public entrypoint for its surface:
Selected from frontend-providers.generated.ts; the generated selectProvider helper is omitted.
// Generated by wildo config sync. Browser CODE only; environment DATA arrives separately.
import type { GeneratedAppProviderModules } from '@wildo-ai/external-connectors-public/app';
import * as providerPackage0 from "@wonder-crm/provider-sentry-frontend/app";
// …
export const generatedFrontendProviderModules: GeneratedAppProviderModules = {
["sentry"]: selectProvider(providerPackage0, "sentry"),
};
The product passes generatedFrontendProviderModules as frontendProviderModules in its startup options; a website’s page bridges pass their own service’s map to createWebsitePageBridgeIsland. Hydration joins this code with the served Sentry entry, and activation reads the DSN. Each surface’s map imports only that surface’s facet, so a product module never lands in a website bundle. Do not edit the generated file: rerun synchronization after changing a declaration. The module package depends on @sentry/browser itself, so the import the code map makes brings the SDK with it. See runtime separation.
Derive the network policy from the same setting
The Sentry website module declares where to find the connection origin and how to activate its SDK:
Selected from website.ts; surrounding declarations and imports are omitted.
csp: {
derivedOrigins: [
{ publicConfigKey: 'dsn', bucket: ProviderFrontendCspOriginBucket.CONNECT },
],
},
sdk: {
descriptor: { packageName: '@sentry/browser' },
async activate({ providerRef, publicConfig }) {
const dsn = publicConfig.dsn;
if (typeof dsn !== 'string' || dsn.length === 0) {
throw new Error(
`Provider "${providerRef}" needs a Sentry DSN in its publicConfig.dsn. Without one the SDK starts `
+ 'and reports nothing, which is indistinguishable from a healthy site. Supply it under '
+ "providerConfigurations.sentry.frontend_sdk in the application's wildo.saas.config.ts.",
);
}
// DYNAMIC on purpose: `@sentry/browser` is this facet's own dependency (the module pins it, #2787), and a dynamic
// import makes it its own chunk, so the page pays for it only once the provider activates. A browser entry's eager
// closure may not reach it (publish refuses that).
const Sentry = await import('@sentry/browser');
const client = Sentry.init({ dsn });
return {
handle: client,
deactivate: async () => {
await client?.close();
},
};
},
},
This activation validates a nonempty DSN, dynamically imports the SDK and returns a handle plus deactivation callback. The DSN here is public client configuration, not a backend API secret. Its origin is derived rather than hardcoded into a separate application list.
Hydration compares descriptors and CSP declarations so stale generated code/configuration does not silently masquerade as agreement. Activation failures name the provider and are isolated from other provider activations.
Use browser-safe entrypoints for the appropriate surface. The backend facet may share the vendor identity, but its secret-aware module does not belong in the browser bundle. Public configuration is visible to visitors by design; never use it to transport tenant or deployment credentials.