Skip to content

ReforgeHQ/sdk-javascript

Repository files navigation

@reforge-com/javascript

A client for Reforge

Installation

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.

Usage in your app

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'));

Client API

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.

Configuration

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"
});

Usage

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...");
}

How it works

When you call getLogLevel(loggerName), the method:

  1. Looks up the configured logger key (default: "log-levels.default")
  2. Returns the appropriate LogLevel enum value (TRACE, DEBUG, INFO, WARN, ERROR, or FATAL)
  3. Returns LogLevel.DEBUG as 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.

logger - Simple Logging Methods

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.

Available Methods

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

How it Works

Each logger method:

  1. Checks the configured log level from the logger key (default: "log-levels.default")
  2. Compares the message level against the configured level
  3. Only outputs to the console if the level is enabled

Console Method Mapping:

  • trace() and debug()console.debug()
  • info()console.info()
  • warn()console.warn()
  • error() and fatal()console.error()

Example

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 enum values

  • LogLevel.TRACE (1) - Most verbose
  • LogLevel.DEBUG (2)
  • LogLevel.INFO (3)
  • LogLevel.WARN (5)
  • LogLevel.ERROR (6)
  • LogLevel.FATAL (9) - Least verbose

Comparing LogLevel values

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");
}

Example integration

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

Usage in your test suite

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/);
});

Contributing

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

About

@reforge-com/javascript public package

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 6