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);Initialization with Context with the <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'));| 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 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 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:
- Looks up the configured logger key (default:
"log-levels.default") - Returns the appropriate
LogLevelenum value (TRACE, DEBUG, INFO, WARN, ERROR, or FATAL) - Returns
LogLevel.DEBUGas the default if no configuration is found
Note: 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.
The 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 logEach logger method:
- Checks the configured log level from the logger key (default:
"log-levels.default") - Compares the message level against the configured level
- Only outputs to the console if the level is enabled
Console Method Mapping:
trace()anddebug()→console.debug()info()→console.info()warn()→console.warn()error()andfatal()→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 consoleLogLevel.TRACE(1) - Most verboseLogLevel.DEBUG(2)LogLevel.INFO(3)LogLevel.WARN(5)LogLevel.ERROR(6)LogLevel.FATAL(9) - Least verbose
Since 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 verboseAfter 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 contextIn 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