Frontend Configuration Distribution
Oidis keeps application configuration in the project configuration system — package.conf.jsonp together with its imports (most notably commons.jsonp) and optional private.conf.jsonp overrides. The Builder merges all layers into a single configuration tree. The frontend runs in the user's browser and must never receive database credentials, API keys, or tokens. The embedFeatures mechanism solves this by requiring developers to explicitly declare which configuration values the frontend needs — only those values are delivered to the browser.
Backend vs Frontend Boundary
The backend has access to the full merged project configuration — database connections, SMTP passwords, authentication secrets, Sentry tokens, and everything else the server needs. The frontend, on the other hand, only receives a small, explicitly whitelisted subset defined by embedFeatures.
Never assume a configuration value is "hidden" on the frontend. Everything delivered to the browser is fully readable via DevTools. Only values listed in embedFeatures are shipped, and even those are subject to automatic password/token stripping by the Builder.
Configuring embedFeatures
The embedFeatures property is defined in package.conf.jsonp under the target section. It accepts two forms.
In practice, embedFeatures is typically defined in commons.jsonp (which is imported into package.conf.jsonp). However, it can be defined or overridden in any configuration layer — package.conf.jsonp directly, commons.jsonp, or even private.conf.jsonp. The Builder resolves the full config chain before processing embedFeatures, so the standard JSONP composition mechanisms (extendsConfig, imports) apply.
Array Form
The simplest form — a flat list of dot-paths:
Io.Oidis.Builder.DAO.Resources.Data({
// ...
target: {
embedFeatures: [
"project.sentry",
"project.lcpConfig",
"project.target.withFrontendHeaders"
]
}
})
Internally, the Builder normalizes the array form to { default: [...] }.
Object Form (Grouped)
When many values are embedded, organize them into named groups for readability:
Io.Oidis.Builder.DAO.Resources.Data({
// ...
target: {
embedFeatures: {
commonsFeatures: [
"project.dbConfigs.dbName",
"project.sentry",
"project.target.withFrontendHeaders",
"project.lcpConfig"
],
hubFeatures: [
"project.target.headerStrip"
],
authFeatures: [
"project.authProviders.google.enabled"
]
}
}
})
Groups are purely organizational. At processing time, the Builder flattens all groups into a single array. Use groups to keep configuration readable when embedding many values.
Dot-Path Syntax
Each entry is a dot-separated path that navigates the merged configuration tree. The path determines how deep the extraction goes — a shallow path extracts an entire subtree, a deep path extracts a single value.
| Dot-path | Extracts |
|---|---|
project.sentry | Entire Sentry config object (DSN, environment, etc.) |
project.dbConfigs.dbName | Database name string only (not credentials) |
project.target.headerStrip | Header strip configuration |
project.authProviders.google.enabled | Single boolean — whether Google auth is enabled |
Distribution Pipeline
Configuration flows from the backend to the frontend through a multi-stage pipeline:
package.conf.jsonp Builder (build-time) Loader.js (runtime) Frontend code
embedFeatures → extract & sanitize → template injection → EnvironmentArgs
Builder Processing (Build-Time)
The Builder (EnvironmentArgs.ts in io-oidis-builder) processes embedFeatures in four steps:
- Normalize — If
embedFeaturesis an array, wrap it into{ default: [...] } - Flatten — Merge all named groups into a single array of dot-paths
- Extract — For each dot-path, walk the merged config tree, extract the value, and reconstruct the nested path into a
clientConfigobject - Sanitize — Strip any keys matching
*pass*or*token*patterns (case-insensitive)
The resulting clientConfig object contains only the whitelisted values:
// Conceptual result after Builder processing
clientConfig = {
sentry: { dsn: "https://example@sentry.io/1", environment: "production" },
dbConfigs: { dbName: "IoOidisMyProject" },
target: { headerStrip: true, withFrontendHeaders: false }
}
The Builder automatically removes any keys whose name contains pass or token (case-insensitive, wildcard match) from embedded values. If a subtree like project.sentry contains a token field, that field is silently removed and a warning is logged during build. This is a safety net — do not rely on it as your only security measure. Always embed specific leaf values rather than broad subtrees that might contain secrets.
Loader.js Template Injection
The extracted clientConfig is injected into Loader.js (located in io-oidis-commons/resource/javascript/) via template variables.
Browser execution path:
let clientConfig = JSON.parse("<? @var clientConfig ?>");
Server (Node.js) execution path:
globalThis.clientConfig = "<? @var clientConfig ?>";
globalThis.embedFeatures = "<? @var project.target.embedFeatures ?>".split(",");
The <? @var ... ?> template variables are replaced by the Builder at build time with the serialized configuration values.
LoaderResolver (Runtime Re-evaluation)
On the backend, LoaderResolver (in io-oidis-localhost) is a resolver registered at the route /resource/javascript/loader.min.js. When the browser requests the loader script, this resolver dynamically re-evaluates embedFeatures against the live configuration — not just the build-time snapshot.
The resolver uses the same dot-path extraction and security validation logic as the Builder:
- Parse the
embedFeatureslist fromglobalThis.embedFeatures - For each path, extract the value from the current
EnvironmentArgsproject config - Apply the same
*pass*/*token*sanitization - Inject the result as
globalThis.clientConfig = JSON.parse("...")
The LoaderResolver runtime re-evaluation means configuration changes (in commons.jsonp or any other config layer) take effect on the frontend after a server restart — without requiring a full rebuild. However, adding new dot-paths to embedFeatures itself still requires a rebuild, since that list is defined in package.conf.jsonp.
Frontend Access
Frontend code accesses the embedded configuration through EnvironmentArgs:
// In any controller or panel
const config = this.getEnvironmentArgs().getProjectConfig();
// Access embedded values (type-safe via IProject interface)
const sentryDsn = config.sentry?.dsn;
const dbName = config.dbConfigs?.dbName;
const headerStrip = config.target?.headerStrip;
The getProjectConfig() method returns an IProject interface, providing type-safe access to the embedded values. Only values that were listed in embedFeatures will be present — all other properties will be undefined.
Security
| Aspect | Mechanism |
|---|---|
| What reaches the frontend | Only values explicitly listed in embedFeatures |
| Password/token stripping | Automatic removal of keys matching *pass* or *token* — at both build-time (Builder) and runtime (LoaderResolver) |
| Backend config viewing | Web Crypto API E2E encryption (AES-GCM) via RuntimeToolController |
| BE/FE boundary | Full merged project configuration on backend; whitelisted subset via embedFeatures on frontend |
The embedFeatures whitelist is your primary security boundary. Never embed paths that contain or could contain secrets (e.g., project.database which includes the connection string). Embed only the specific leaf values or safe subtrees the frontend genuinely needs.
RuntimeToolController
Oidis Hub GUI provides an admin panel (RuntimeToolController in io-oidis-hub-gui) that allows administrators to inspect the runtime configuration of a running instance.
Frontend Config tab displays the embedded clientConfig as plaintext JSON. This is safe because it only contains values already shipped to every browser via embedFeatures.
Backend Config tab uses Web Crypto API with AES-GCM end-to-end encryption to securely display the full backend configuration:
- The browser generates an ephemeral AES-256-GCM key
- The key (as JWK) and initialization vector are sent to the backend
- The backend encrypts the full project config with the provided key and returns the ciphertext
- The browser decrypts the response in memory for display
- The decrypted config is never stored or transmitted — it exists only in the browser's memory during viewing
The RuntimeToolController is available only to administrators in io-oidis-hub-gui. It provides a secure way to verify the runtime configuration without accessing server files directly.
Quick Reference
| What | Where | Package |
|---|---|---|
| Declare embedded paths | target.embedFeatures in package.conf.jsonp | Your project |
| Build-time extraction | EnvironmentArgs.ts | io-oidis-builder |
| Runtime distribution | Loader.js | io-oidis-commons |
| Runtime re-evaluation | LoaderResolver.ts | io-oidis-localhost |
| Frontend access | EnvironmentArgs.getProjectConfig() | io-oidis-commons |
| Admin config viewer | RuntimeToolController.ts | io-oidis-hub-gui |
See also: JSONP System | Commons Config | Architecture Overview | HTML and Front End Loader