Skip to content

Repository files navigation

AGL.js | Epic Active Guidelines

A typed client for Epic's Active Guidelines (AGL) platform. Call do() to run actions, listen for events, and read session details — the handshake, tokens, and message plumbing are handled for you.

It handles:

  • Detecting whether the app is running inside Epic
  • Communicating with the Epic backend
  • Triggering and queuing actions in Hyperspace
  • Subscribing to and handling AGL events, including print requests
  • App state and navigation history
  • Clean teardown of the persistent message listener

Table of Contents

  1. Getting Started
  2. Configuration
  3. Usage
  4. Parameters
  5. Methods
  6. Queuing
  7. Error Handling
  8. Subscriptions
  9. State Management
  10. Navigation History
  11. Teardown
  12. Migrating from 1.x

Disclaimer

AGL.js is not affiliated with or endorsed by Epic Systems Corporation. It is intended solely for use by organizations with active Epic licenses. Please ensure compliance with your Epic agreements before using or distributing this library.

Getting Started

📦 Installation

Install via npm or yarn:

npm install agljs
# or
yarn add agljs

TypeScript

import AGL from 'agljs'

const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)

JavaScript (ESM)

import AGL from 'agljs'

const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)

JavaScript (CommonJS)

const AGL = require('agljs')

const agl = new AGL()
agl.active.then(active => active && console.log(agl.details.availableActions))

💾 Script Tag (download or CDN)

Download agl.min.js from the releases page, or load it from a CDN such as jsDelivr:

The script tag exposes the class as the global AGL:

<!-- local: <script src="path/to/agl.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/agljs/js/agl.min.js"></script>
<script>
  const agl = new AGL();

  agl.active.then(function(active) {
    if (active) console.log(agl.details.availableActions);
  });
</script>

Configuration

The AGL constructor accepts an optional configuration object with the following optional parameters:

Parameter Type Description Default
debug boolean Enables debug logging for troubleshooting false
timeout number Timeout (in milliseconds) before failure for action responses 2000
targetOrigin string Restricts inbound/outbound messages to a specific Epic origin '*'
subscribe object An object containing events to subscribe to during the handshake {}
onError function Callback for handling errors null
onNavigate function Callback for navigation events (e.g., back/forward) null
onReload function Callback for reload (history package) events null
onAGLEvent function Callback for custom AGL events null
onSubscribed function Callback for handling subscription results null
onPrint function Callback fired when Epic requests a print null

Debugging tip: set debug: true in the constructor as your first step. Because the handshake runs at construction, only a constructor-time debug flag captures it — this is the most useful log for diagnosing initialization.

Note: subscribe and targetOrigin are fixed at first construction. Re-constructing AGL updates only debug, timeout, and callbacks.

Example Configuration

const agl = new AGL({
   debug: true,
   timeout: 5000,
   subscribe: {
      "Epic.Common.RequestToCloseApp": { PauseDuration: 300 },
   },
   onError: (error) => console.error('Error:', error),
   onNavigate: (event) => console.log('Navigation event:', event.direction),
   onReload: (payload) => console.log('Reload payload:', payload),
   onPrint: () => window.print(),
});

Usage

All actions must wait for the AGL handshake to complete and confirm the app is running inside Epic, via if (await agl.active).

Example:

const agl = new AGL();

if (await agl.active) {
   agl.do('Epic.Clinical.Informatics.Web.SaveState', { state: 'example' })
      .then((success) => console.log('State saved:', success))
      .catch((err) => console.error('Error saving state:', err));
}

Example:

const agl = new AGL();
if (!await agl.active) throw new Error('AGL is inactive! Not running in Epic.');

agl.on('navigate', (event) => {
   console.log('User navigated:', event.direction);
});

Parameters

active (read-only)

Always returns a Promise<boolean> that resolves to whether AGL is active and initialized. await it before performing actions:

if (await agl.active) console.log('AGL is initialized and ready.');

details (read-only)

Provides information from Epic about the current AGL context. Properties include:

Property Type Description
availableActions string[] List of actions supported in the current context
actionsNotRequiringUserConfirmation string[] Actions Epic will run without a confirmation prompt
currentState string | null State restored from the handshake (see below)
interfaceVersion string | null Version of the AGL JavaScript interface
readOnly boolean Indicates if the AGL context is read-only
token string | null Token required for posting messages to Epic

Example:

console.log('Token:', agl.details.token);
console.log('Available actions:', agl.details.availableActions.join(', '));

debug (write-only)

Enables or disables debug logging dynamically

Example:

agl.debug = true; // Enable debug logging

Methods

do(action, args = null, haltOnError = false)

Executes an action within AGL.

Parameter Type Description
action string The name of the action to perform
args object Optional Arguments to pass with the action
haltOnError boolean Optional Stops the queue if this action fails, preventing subsequent actions from running

do() automatically waits for the handshake to finish before running, so you do not need to guard every call with await agl.active. It resolves false if AGL never initializes (e.g. not running in Epic).

Example: Basic Action

agl.do('SaveState', { state: 'example' });

For convenience, actions without a full namespace (i.e., actions without a dot) are automatically prefixed with Epic.Clinical.Informatics.Web. To override this behavior, include the full namespace directly:

Example: Full Namespace Action

if (await agl.active) {
   agl.do('Custom.Namespace.Action', { someArg: 'value' });
}

on(eventName, callback)

Registers a callback for specific AGL events.

Parameter Type Description
eventName string The name of the event to listen for
callback function The callback function to execute when the event occurs

Events types:

Event Name Example Response
error { message: 'Error description', details: ['Detail 1', ...] }
navigate { direction: 'Back' }
reload The raw historyPackage payload from Epic (see note below)
aglEvent { name: 'Event name', args: { arg1: ... } }
subscribed { EventName: 'Event name', SubscriptionSuccess: boolean }
print (no arguments) — fired when Epic requests a print

reload payload: Epic delivers the navigation-history package under historyPackage. Its inner shape is passed through unchanged as a Record<string, unknown>; inspect it against your Epic build (enable debug) rather than assuming key names. This is distinct from state restoration — see State Management.

Example:

agl.on('navigate', (event) => {
   console.log('User navigated:', event.direction);
});

Example: Chaining listeners

agl
   .on('navigate', (event) => console.log('User navigated:', event.direction))
   .on('reload', (payload) => console.log('History package:', payload));

Queuing

Actions are added to a queue and executed sequentially to avoid race conditions. By default, the queue continues processing even if an action fails. However, setting haltOnError in do to true stops the queue when that action fails, ensuring dependent actions are not executed. A timeout is treated as a lost connection: it tears the session down entirely (see Teardown).

Example:

agl.do('SaveState', { state: 'example' }, true) // Stop queue if SaveState fails
   .catch((error) => console.error('SaveState failed:', error));

agl.do('CloseActivity', null) // Will only execute if SaveState succeeds
   .catch((error) => console.error('CloseActivity failed:', error));

Advanced Usage

Error Handling

Without an error handler, a failed action rejects with an Error. With a handler, the callback receives the structured error and the action resolves false. If the handler throws, the action rejects with that error.

Overriding Error Handling

You can override internal error handling by providing a custom onError callback in the configuration.

Example: During instantiation

const agl = new AGL({
   onError: (error) => {
      console.error('Custom error handler:', error.message);
   },
});

Example: After instantiation

agl.on('error', (error) => {
   console.error('Custom error handler:', error.message);
});

Subscriptions

You can specify subscriptions during initialization using the subscribe property. Subscriptions allow your application to register for specific events, such as changes in patient demographics or requests to close the app.

Confirming Subscriptions

To confirm that each subscription was successfully processed during the handshake, you can define an onSubscribed callback. This callback receives the subscription results, allowing you to verify whether your subscriptions were accepted by Epic.

Example:

const agl = new AGL({
   subscribe: {
      "Epic.Patient.Demographics.Updated": { IncludeHistory: true },
      "Epic.Common.RequestToCloseApp": { PauseDuration: 200 }
   },
   onSubscribed: (results) => {
      console.log('Subscription handshake completed:', results);
   },
});

Listening for Subscribed Events

After subscriptions are successfully established, your application can handle the events triggered by those subscriptions using the aglEvent handler.

Example:

agl.on('aglEvent', (event) => {
   if (event.name === 'Epic.Patient.Demographics.Updated') {
      console.log('Patient demographics updated:', event.args);
   }
   if (event.name === 'Epic.Common.RequestToCloseApp') {
      agl.do('Epic.Common.CloseApp',  {
         CanClose: true
      });
   }
});

State Management

AGL.js supports saving and restoring app state during transitions or hibernation.

Saving State

To save the current state of your application, use the SaveState action.

Example: Simple string

agl.do('SaveState', { state: 'dashboard' });

Example: Complex state

agl.do('SaveState', {
   state: JSON.stringify({ tab: 'overview', filters: { active: true } })
});

Restoring State

Epic returns the previously saved state on the handshake, exposed as details.currentState. Read it once AGL is active — it is null when no state was saved:

Example:

const agl = new AGL();

if (await agl.active && agl.details.currentState) {
   const restored = JSON.parse(agl.details.currentState);
   console.log('Restored state:', restored);
   activatePage(restored.tab, restored.filters);
}

State restoration (SaveStatedetails.currentState) is separate from the reload event, which carries Epic's navigation-history package. Do not use reload to restore saved state.

Navigation History

The library supports managing navigation history, tracking user interactions with Back/Forward buttons, and maintaining app-specific navigation states.

Listening to Navigation Events

Use onNavigate to track Back/Forward button clicks.

Example:

agl.on('navigate', ({ direction }) => {
   console.log(`Navigated: ${direction}`);
   // Handle navigation here
});

Saving Navigation History

Save custom navigation states using SaveHistoryState. AGL.js will persist these states for the current session.

Example:

agl.do('Epic.Clinical.Informatics.Web.SaveHistoryState', {
   state: JSON.stringify({ tab: 'dashboard', filters: { active: true } })
});

Disabling Navigation Buttons

Disable Back/Forward buttons in unsupported contexts.

Example:

agl.do('Epic.Clinical.Informatics.Web.SetEnabledHistBtns', {
   Back: false,
   Forward: false
});

Teardown

AGL.js installs one persistent message listener for the session. Call destroy() to remove it, settle any outstanding actions as false, and release the singleton. A later new AGL() then performs a fresh handshake.

Example:

agl.destroy();

The instance is also disposable, so using cleans it up automatically:

{
   using agl = new AGL();
   await agl.active;
   await agl.do('SaveState', { state: 'example' });
} // destroy() runs here

Migrating from 1.x

Change 1.x 2.0
active Returned boolean after init Always returns Promise<boolean> — always await it
reload payload { state, fromHibernation } (unverified) Raw historyPackage object
State restore Documented via reload Read details.currentState after await agl.active
CommonJS require('agljs').default require('agljs')
Script-tag global window.agl window.AGL
Print (none) onPrint / on('print', ...)
Teardown (none) destroy() / [Symbol.dispose]
Runtime ES2022 ES2024 with Symbol.dispose

2.0 targets ES2024 and requires Symbol.dispose (modern Chromium/Edge).

Contributing

Fork, branch, and open a pull request.

Kopimi License License

Kopimi. Free to use, modify, and share. If the code is used, borrowed, modified, or incorporated into a commercial product or service, attribution is required.

About

A lightweight framework for integrating web apps with Epic's Active Guidelines platform, using modern JavaScript features. Manage action queuing, event handling, subscriptions, and state within Epic Hyperspace.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages