
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@reforge-com/javascript
Advanced tools
A client for Reforge
npm install @reforge-com/javascript or yarn add @reforge-com/javascript
If you'd prefer to use the standalone <script> tag approach, we recommend using
jsDelivr for a minified/bundled version.
Initialize reforge with your sdk key and a Context for the current user/visitor/device/request:
import { reforge, Context } from "@reforge-com/javascript";
const options = {
sdkKey: "1234",
context: new Context({
user: {
email: "test@example.com",
},
device: { mobile: true },
}),
};
await reforge.init(options);
<script> tag// `reforge` is available globally on the window object
// `Context` is available globally as `window.reforgeNamespace.Context`
const options = {
sdkKey: "1234",
context: new reforgeNamespace.Context({
user: {
email: "test@example.com",
},
device: { mobile: true },
}),
};
reforge.init(options).then(() => {
console.log(options);
console.log("test-flag is " + reforge.get("test-flag"));
console.log("ex1-copywrite " + reforge.get("ex1-copywrite"));
$(".copywrite").text(reforge.get("ex1-copywrite"));
});
Now you can use reforge's config and feature flag evaluation, e.g.
if (reforge.isEnabled('cool-feature') {
// ...
}
setTimeout(ping, reforge.get('ping-delay'));
To avoid a request waterfall, you can start fetching the configuration early in your app's
lifecycle, before the React SDK or reforge.init() is called.
import { prefetchReforgeConfig, Context } from "@reforge-com/javascript";
prefetchReforgeConfig({
sdkKey: "1234",
context: new Context({
user: {
email: "test@example.com",
},
}),
});
When you later call reforge.init(), it will automatically use the prefetched promise if available.
| property | example | purpose |
|---|---|---|
isEnabled | reforge.isEnabled("new-logo") | returns a boolean (default false) if a feature is enabled based on the current context |
get | reforge.get('retry-count') | returns the value of a flag or config evaluated in the current context |
getDuration | reforge.getDuration('http.timeout') | returns a duration object {seconds: number, ms: number} |
getLogLevel | reforge.getLogLevel("my.app.logger") | returns a LogLevel enum value for the specified logger name |
logger | reforge.logger.info("message") | log messages with dynamic log level control (see below for all methods) |
loaded | if (reforge.loaded) { ... } | a boolean indicating whether reforge content has loaded |
shouldLog | if (reforge.shouldLog(...)) { | returns a boolean indicating whether the proposed log level is valid for the current context |
poll | reforge.poll({frequencyInMs}) | starts polling every frequencyInMs ms. |
stopPolling | reforge.stopPolling() | stops the polling process |
context | reforge.context | get the current context (after init()). |
updateContext | reforge.updateContext(newContext) | update the context and refetch. Pass false as a second argument to skip refetching |
extract | reforge.extract() | returns the current config as a plain object of key, config value pairs |
hydrate | reforge.hydrate(configurationObject) | sets the current config based on a plain object of key, config value pairs |
shouldLog()shouldLog allows you to implement dynamic logging. It takes the following properties:
| property | type | example | case-sensitive |
|---|---|---|---|
loggerName | string | my.corp.widgets.modal | Yes |
desiredLevel | string | INFO | No |
defaultLevel | string | ERROR | No |
If you've configured a level value for the exact loggerName (as log-level.{loggerName}), that
value will be used for comparison against the desiredLevel. If no configured level is found for
the exact loggerName, then the provided defaultLevel will be compared against desiredLevel.
Note: shouldLog does NOT traverse the logger name hierarchy. It only checks for an exact match
of log-level.{loggerName}.
If desiredLevel is greater than or equal to the comparison severity, then shouldLog returns
true. If the desiredLevel is less than the comparison severity, then shouldLog will return
false.
Example usage:
const desiredLevel = "info";
const defaultLevel = "error";
const loggerName = "my.corp.widgets.modal";
if (shouldLog({ loggerName, desiredLevel, defaultLevel })) {
console.info("...");
}
If no log level value is configured in Reforge for the exact key
"log-level.my.corp.widgets.modal", then the defaultLevel ("ERROR") will be used and the
console.info will not happen. If the value is configured for that exact key and is INFO or more
verbose, the console.info will happen.
getLogLevel()getLogLevel provides a simpler way to get log levels for dynamic logging. It returns a LogLevel
enum value from a configured key.
You can optionally specify a custom logger key during initialization (default is
"log-levels.default"):
await reforge.init({
sdkKey: "1234",
context: new Context({
/* ... */
}),
loggerKey: "my.custom.log.config", // optional, defaults to "log-levels.default"
});
import { reforge, LogLevel } from "@reforge-com/javascript";
const loggerName = "my.app.widgets.modal";
const level = reforge.getLogLevel(loggerName);
// level is a LogLevel enum value
if (level === LogLevel.DEBUG || level === LogLevel.TRACE) {
console.debug("Debug information...");
}
When you call getLogLevel(loggerName), the method:
"log-levels.default")LogLevel enum value (TRACE, DEBUG, INFO, WARN, ERROR, or FATAL)LogLevel.DEBUG as the default if no configuration is foundNote: The loggerName parameter is currently only used for potential telemetry/logging
purposes. All loggers share the same configured log level from the logger key. For per-logger log
levels, use shouldLog() with individual log-level.{loggerName} configs.
logger - Simple Logging MethodsThe reforge.logger object provides convenient methods for logging at different levels. These
methods automatically check the configured log level and only output to the console when
appropriate.
import { reforge } from "@reforge-com/javascript";
// Configure the log level
await reforge.init({
sdkKey: "1234",
context: new Context({
/* ... */
}),
loggerKey: "log-levels.default", // optional
});
reforge.hydrate({ "log-levels.default": "INFO" });
// Use the logger methods
reforge.logger.trace("Trace message"); // Will not log (below INFO)
reforge.logger.debug("Debug message"); // Will not log (below INFO)
reforge.logger.info("Info message"); // Will log
reforge.logger.warn("Warning message"); // Will log
reforge.logger.error("Error message"); // Will log
reforge.logger.fatal("Fatal message"); // Will log
Each logger method:
"log-levels.default")Console Method Mapping:
trace() and debug() → console.debug()info() → console.info()warn() → console.warn()error() and fatal() → console.error()import { reforge, Context } from "@reforge-com/javascript";
await reforge.init({
sdkKey: "your-key",
context: new Context({ user: { id: "123" } }),
});
// Set log level to WARN
reforge.hydrate({ "log-levels.default": "WARN" });
reforge.logger.debug("Debug details"); // Not logged
reforge.logger.info("Process started"); // Not logged
reforge.logger.warn("Low disk space"); // Logged to console
reforge.logger.error("Failed to save"); // Logged to console
LogLevel.TRACE (1) - Most verboseLogLevel.DEBUG (2)LogLevel.INFO (3)LogLevel.WARN (5)LogLevel.ERROR (6)LogLevel.FATAL (9) - Least verboseSince LogLevel is a string enum, you can't use <= directly. Use the provided helper functions:
import { reforge, LogLevel, shouldLogAtLevel, getLogLevelSeverity } from "@reforge-com/javascript";
const configuredLevel = reforge.getLogLevel("my.app.logger");
// Option 1: Use shouldLogAtLevel helper (recommended)
if (shouldLogAtLevel(configuredLevel, LogLevel.DEBUG)) {
console.debug("Debug message");
}
// Option 2: Compare severity values
if (getLogLevelSeverity(configuredLevel) <= getLogLevelSeverity(LogLevel.INFO)) {
console.info("Info message");
}
import { reforge, LogLevel, shouldLogAtLevel } from "@reforge-com/javascript";
class Logger {
constructor(name) {
this.name = name;
}
debug(message) {
const level = reforge.getLogLevel(this.name);
if (shouldLogAtLevel(level, LogLevel.DEBUG)) {
console.debug(`[${this.name}] ${message}`);
}
}
info(message) {
const level = reforge.getLogLevel(this.name);
if (shouldLogAtLevel(level, LogLevel.INFO)) {
console.info(`[${this.name}] ${message}`);
}
}
error(message) {
const level = reforge.getLogLevel(this.name);
if (shouldLogAtLevel(level, LogLevel.ERROR)) {
console.error(`[${this.name}] ${message}`);
}
}
}
// Usage
const logger = new Logger("my.app.components.modal");
logger.debug("Modal opened"); // Only logs if DEBUG level is enabled for this logger
logger.info("User action completed"); // Only logs if INFO level or more verbose is enabled
logger.error("Failed to save"); // Logs for ERROR level or more verbose
poll()After reforge.init(), you can start polling. Polling uses the context you defined in init by
default. You can update the context for future polling by setting it on the reforge object.
// some time after init
reforge.poll({frequencyInMs: 300000})
// we're now polling with the context used from `init`
// later, perhaps after a visitor logs in and now you have the context of their current user
reforge.context = new Context({...reforge.context, user: { email: user.email, key: user.trackingId })
// future polling will use the new context
In your test suite, you probably want to skip the reforge.init altogether and instead use
reforge.setConfig to set up your test state.
it("shows the turbo button when the feature is enabled", () => {
reforge.setConfig({
turbo: true,
defaultMediaCount: 3,
});
const rendered = new MyComponent().render();
expect(rendered).toMatch(/Enable Turbo/);
expect(rendered).toMatch(/Media Count: 3/);
});
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. For detailed contributing guidelines, please see CONTRIBUTING.md
FAQs
Feature Flags & Dynamic Configuration as a Service
We found that @reforge-com/javascript demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.