@forgerock/login-widget 1.3.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,117 +1,101 @@
1
- # ForgeRock Web Login Framework
2
-
1
+ [![npm version](https://img.shields.io/npm/v/@forgerock/login-widget?color=%23f46200&style=flat-square)](https://www.npmjs.com/package/@forgerock/login-widget)
3
2
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
4
- [![semantic-release: conventional](https://img.shields.io/badge/semantic--release-conventional-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
3
+
4
+ <p align="center">
5
+ <a href="https://github.com/ForgeRock">
6
+ <img src="https://www.forgerock.com/themes/custom/forgerock/images/fr-logo-horz-color.svg" alt="ForgeRock Logo">
7
+ </a>
8
+ <h2 align="center">ForgeRock Login Widget</h2>
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@forgerock/login-widget">npm</a>
11
+ ·
12
+ <a href="#support">Support</a>
13
+ ·
14
+ <a href="https://backstage.forgerock.com/docs/sdks/latest/javascript/webloginframework.html">Docs</a>
15
+ </p>
16
+ <hr/>
17
+ </p>
5
18
 
6
19
  ## Table of Contents
7
20
 
8
21
  - [Overview](#overview)
9
22
  - [Requirements](#requirements)
10
- - [Quick Start: Using the Widget in Your App](#quick-start-using-the-widget-in-your-app)
11
- - [Installing the package](#installing-the-package)
12
- - [Adding the Widget's CSS](#adding-the-widgets-css)
13
- - [Using the Widget component](#using-the-widget-component)
14
- - [What you need to know](#what-you-need-to-know)
15
- - [Import the API Object](#import-the-api-object)
16
- - [Call API method to initiate observable](#call-api-method-to-initiate-observable)
17
- - [Use the subscribe method](#use-the-subscribe-method)
18
- - [Unsubscribing from an Observable](#unsubscribing-from-an-observable)
19
- - [Getting the current, local value](#getting-the-current-local-value)
20
- - [Requesting values from the ForgeRock platform](#requesting-values-from-the-forgerock-platform)
21
- - [User Promises](#using-promises)
23
+ - [Quick Start](#quick-start)
24
+ - [Install](#install)
25
+ - [Add CSS](#add-css)
26
+ - [Mount the Widget](#mount-the-widget)
27
+ - [Observables Pattern](#observables-pattern)
28
+ - [Subscribe to Events](#subscribe-to-events)
29
+ - [Unsubscribe](#unsubscribe)
30
+ - [Get Current Values](#get-current-values)
31
+ - [Request Fresh Values](#request-fresh-values)
32
+ - [Use Promises](#use-promises)
22
33
  - [Complete Widget API](#complete-widget-api)
23
34
  - [Widget](#widget)
24
35
  - [Configuration](#configuration)
25
36
  - [Journey](#journey)
26
37
  - [Component](#component)
27
38
  - [User](#user)
28
- - [Request](#request)
29
- - [Styling configuration](#styling-configuration)
30
- - [Links configuration](#links-configuration)
31
- - [Content configuration](#content-configuration)
32
- - [Currently unsupported](#currently-unsupported)
39
+ - [Styling Configuration](#styling-configuration)
40
+ - [Links Configuration](#links-configuration)
41
+ - [Content Configuration](#content-configuration)
42
+ - [Supported Callbacks](#supported-callbacks)
33
43
  - [Disclaimer](#disclaimer)
34
44
  - [License](#license)
35
45
 
36
46
  ## Overview
37
47
 
38
- The Login Widget produced by this framework is intended to be an all-inclusive, UI component that can be used within any modern JavaScript app for handling the default login, registration and related user flows. It can be used within a React, Vue, Angular or any other modern JavaScript framework (does not currently support Node.js or server-rendering (SSR)).
48
+ The Login Widget is an all-inclusive UI component for handling login, registration, and related user flows in any modern JavaScript app. It works with React, Vue, Angular, Svelte, or vanilla JavaScript it does not currently support Node.js or server-side rendering (SSR).
39
49
 
40
- This Widget uses the ForgeRock JavaScript SDK internally. It adds a UI rendering layer on top of the SDK to help eliminate the need to develop and maintain the UI components necessary for providing complex authentication flows. Although this rendering layer is developed with Svelte and Tailwind, it is "compiled away" and has no runtime dependencies. It is library and framework agnostic.
50
+ The widget uses [Journey Client](https://developer.pingidentity.com/orchsdks/journey/usage/javascript/index.html) for journey execution, and [OIDC Client](https://developer.pingidentity.com/orchsdks/oidc/usage/javascript-centralized-login.html) for OAuth/OIDC tokens and user info. It adds a UI rendering layer on top of these SDKs to eliminate the need to develop and maintain UI components for complex authentication flows. Although this rendering layer is developed with Svelte and Tailwind, both are "compiled away" and have no runtime dependencies. The resulting widget is library- and framework-agnostic.
41
51
 
42
- This Widget can be rendered in two different types (or "form factors"):
52
+ The widget can be rendered in two form factors:
43
53
 
44
- 1. **Modal** type: this is the default and the recommended way to use the widget at first. It renders the form elements inside a modal dialog that can be opened and closed. This component is mounted _outside_ of your app's controlled DOM.
45
- 2. **Inline** type: this is just the form elements themselves, no container. This component is intended to be rendered _inside_ your app's controlled DOM.
54
+ 1. **Modal** (default): Renders form elements inside a modal dialog that opens and closes. Mounted _outside_ your app's controlled DOM.
55
+ 2. **Inline**: Just the form elements with no container. Mounted _inside_ your app's controlled DOM.
46
56
 
47
- Both components provide the same authentication, OAuth/OIDC and user features. The only difference is how the component is rendered within your app.
48
-
49
- It is highly recommended to start with the Modal form factor when in the experimenting or prototyping phase. It provides the quickest development experience for providing login and registration flows into your app with the least disruption to your existing codebase. The Modal will be controlled within your app, but rendered in its own DOM root node and visual layer.
57
+ Both form factors provide the same authentication, OAuth/OIDC, and user features. Start with Modal it provides the quickest path to adding login/registration with minimal disruption to your existing codebase.
50
58
 
51
59
  ## Requirements
52
60
 
53
- 1. A EcmaScript Module or CommonJS enable client-side JavaScript app
54
- 2. A "modern", fully-supported browser: Chrome, Firefox, Safari, Chromium Edge (see below for example of what's not supported)
61
+ - An ECMAScript module or CommonJS enabled client-side JavaScript app
62
+ - A modern, fully-supported browser: Chrome, Firefox, Safari, or Chromium Edge
55
63
 
56
- What's not supported? Internet Explorer, Legacy Edge, WebView, Electron and other modified, browser-like environments are _not_ supported.
64
+ **Not supported**: Internet Explorer, Legacy Edge, WebView, Electron, and other modified browser-like environments.
57
65
 
58
- ## Quick Start: Using the Widget in Your App
66
+ ## Quick Start
59
67
 
60
- ### Installing the package
68
+ ### Install
61
69
 
62
70
  ```shell
63
71
  npm install @forgerock/login-widget
64
72
  ```
65
73
 
66
- ### Adding the Widget's CSS
67
-
68
- There are a few ways to add the Widget's CSS to your product:
69
-
70
- 1. Import it into your JavaScript project as a module
71
- 2. Import it using a CSS preprocessor, like Sass, Less or PostCSS
72
- 3. Copy the CSS file from the package and link it into your HTML
73
-
74
- If you decide to import the CSS into your JavaScript, make sure your bundler knows how to import and process the CSS as a module. If using a CSS preprocessor, ensure you configure your preprocessor to access files from within your `package/` directory.
75
-
76
- Copying the file and pasting it into your project for linking in the HTML is the easiest.
74
+ ### Add CSS
77
75
 
78
- Importing into your JavaScript:
76
+ Import into your JavaScript:
79
77
 
80
78
  ```js
81
- // app.js
82
79
  import '@forgerock/login-widget/widget.css';
83
80
  ```
84
81
 
85
- Importing into your CSS:
82
+ Import into your CSS:
86
83
 
87
84
  ```css
88
- /* style.css */
89
85
  @import '@forgerock/login-widget/widget.css';
90
86
  ```
91
87
 
92
- Linking CSS in HTML example (you may have to copy the CSS file out of the npm module and into your static files directory):
88
+ Or link in HTML (copy the CSS file from the npm module into your static files):
93
89
 
94
90
  ```html
95
- <!-- index.html -->
96
- <!doctype html>
97
- <html lang="en">
98
- <head>
99
- <!-- ... -->
100
- <link rel="stylesheet" href="/path/to/file/widget.css" />
101
- </head>
102
- <body>
103
- <!-- ... -->
104
- </body>
105
- </html>
91
+ <link rel="stylesheet" href="/path/to/file/widget.css" />
106
92
  ```
107
93
 
108
- #### Controlling the CSS cascade
94
+ #### Controlling the CSS Cascade
109
95
 
110
- Though not required, this helps solve common style issues that may pop up related to the CSS cascade. Using `@layer` will ensure the browser applies the CSS in the way you intend, regardless of the order you import or declare the CSS in your project. You can [read more about this new browser feature in the Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer).
96
+ Though not required, using `@layer` ensures the browser applies CSS in the intended order regardless of import order. [Read more about @layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer).
111
97
 
112
- Steps recommended:
113
-
114
- 1. Wrap your current CSS in a layer called `app`:
98
+ 1. Wrap your CSS in a layer:
115
99
 
116
100
  ```css
117
101
  @layer app {
@@ -119,14 +103,11 @@ Steps recommended:
119
103
  }
120
104
  ```
121
105
 
122
- Widget layers are already declared within the Widget's CSS.
123
-
124
- 2. Declare the order of layers in your index HTML file before any CSS is loaded:
106
+ 2. Declare layer order in your HTML before any CSS loads:
125
107
 
126
108
  ```html
127
109
  <style type="text/css">
128
110
  @layer app;
129
- /* List the Widget layers last */
130
111
  @layer 'fr-widget.base';
131
112
  @layer 'fr-widget.utilities';
132
113
  @layer 'fr-widget.components';
@@ -134,246 +115,69 @@ Steps recommended:
134
115
  </style>
135
116
  ```
136
117
 
137
- It's important to note that none of the CSS imported for the Widget will overwrite any of your app's CSS. It's all namespaced to ensure there are no collisions. Unless, that is, you use the exact same selector naming convention we use.
138
-
139
- ### Using the Widget component
118
+ Widget CSS is fully namespaced it will not overwrite your app's CSS unless you use the same selector naming convention.
140
119
 
141
- #### Add element to your HTML file
120
+ ### Mount the Widget
142
121
 
143
- We recommend you add a new element on which you will mount the Widget to your static HTML file. For most SPAs (Single Page Applications), this will be your `index.html`. This new element should be a direct child element of the body, and not without the element you mount your SPA.
144
-
145
- Example:
122
+ Add a dedicated element to your HTML file as a direct child of `<body>`, separate from your main app root:
146
123
 
147
124
  ```html
148
- <!doctype html>
149
- <html lang="en">
150
- <head>
151
- <!-- ... -->
152
- </head>
153
- <body>
154
- <!-- Root element for main app -->
155
- <div id="root"></div>
156
-
157
- <!-- Root element for Widget -->
158
- <div id="widget-root"></div>
159
-
160
- <!-- scripts ... -->
161
- </body>
162
- </html>
163
- ```
164
-
165
- NOTE: We do not recommend mounting the Widget on a dynamic element that's inside your main application's "controlled DOM". If you're not experienced with mixed rendering types in the Virtual DOM, you may find this challenging at first. So, to ensure success at the start, manually adding it outside of your application's controlled DOM is best. It's recommended to put the element for Widget mounting in your static HTML file as a child of the `<body>` tag.
166
-
167
- #### Instantiate the Widget
168
-
169
- You can import the Widget into your app wherever you would like as a modal dialog (aka "lightbox"), this form factor is the default. Once the Widget is imported, you will need to instantiate it.
170
-
171
- ```js
172
- // As modal dialog
173
- import Widget from '@forgerock/login-widget';
174
-
175
- // ...
176
-
177
- // Grab the root element added to your HTML file
178
- const widgetRootEl = document.getElementById('widget-root');
179
-
180
- // Instantiate Widget with the `new` keyword
181
- new Widget({
182
- target: widgetRootEl, // Any existing element from static HTML file
183
- });
184
- ```
185
-
186
- This instantiates the component and mounts the Widget into the DOM. By default, it will be the Modal version, and will therefore be be hidden at first. Component controls will be discussed shortly.
187
-
188
- Note: [See additional documentation about configuring the JS SDK](https://backstage.forgerock.com/docs/sdks/3.3/javascript/configuring/configuring-forgerock-sdk-settings-for-your-javascript-app.html).
189
-
190
- #### The component lifecycle
191
-
192
- When using the default Modal form factor, the first thing you'll want to do is open it. To do this, you need to import the `component` function. Executing this function will return a `componentEvents` object, you can name this whatever you'd like. Calling the `componentEvents.open` method will trigger the modal to animate into view.
193
-
194
- ```js
195
- import Widget, { component } from '@forgerock/login-widget';
196
-
197
- // ...
198
-
199
- const componentEvents = component();
200
-
201
- new Widget({ target: widgetRootEl }); // Instantiate the Widget
202
-
203
- componentEvents.open(); // Ensure this is called after the Widget has been instantiated
204
-
205
- // A more realistic pattern is calling this within a button click
206
- const loginButton = document.getElementById('loginButton');
207
- loginButton.addEventListener('click', () => {
208
- componentEvents.open();
209
- });
210
- ```
211
-
212
- NOTE: Opening the modal by itself will just reveal the modal with a loading spinner. This is because the Widget has no data to inform it on what to render. We will solve this in the "Starting a journey" section.
213
-
214
- In addition to being able to open the modal, you'll likely want to know when it's mounted, or closed by the user or upon completed authentication. This is done via the same API. A `subscribe` method will be on this `componentEvents` object (which technically makes this an observable), and all component related events will trigger the `subscribe` function to be called passing the `event` state to your callback function.
215
-
216
- ```js
217
- componentEvents.subscribe((event) => {
218
- if (event.mounted) {
219
- console.log('Widget is mounted!');
220
- }
221
- });
222
- ```
223
-
224
- When the user successfully authenticates, the modal will close itself and emit an event. You can detect this within your `subscribe` callback function and the `event` object.
225
-
226
- ```js
227
- componentEvents.subscribe((event) => {
228
- if (event.mounted) {
229
- console.log('Widget is mounted!');
230
- }
231
-
232
- if (event.open === false) {
233
- console.log(event.reason); // The reason for closing will be provided
234
- }
235
- });
125
+ <body>
126
+ <div id="root"></div>
127
+ <div id="widget-root"></div>
128
+ </body>
236
129
  ```
237
130
 
238
- If you'd like to close the modal programmatically, you can via the `componentEvents.close` method.
131
+ > **Tip**: Do not mount the modal form factor on a dynamic element inside your app's Virtual DOM. Hard-code it in your static HTML file.
239
132
 
240
- #### Configuring the Widget
241
-
242
- Before the Widget can interact with the ForgeRock platform, it will need to be configured. We have a `configuration` function for this API.
243
-
244
- First, import the `configuration` function from the module and call it to receive the config API. This API has a single method called `set`. Let's start with the minimal configuration.
133
+ Import, configure, and instantiate:
245
134
 
246
135
  ```js
247
- import { configuration } from '@forgerock/login-widget';
248
-
249
- const config = configuration();
250
- config.set({
251
- forgerock: {
252
- serverConfig: {
253
- baseUrl: 'https://example.forgeblocks.com/am/', // This needs to be your AM URL
254
- timeout: 3000, // In milliseconds; 3 to 5 seconds should be fine
255
- },
136
+ import Widget, { configure, journey } from '@forgerock/login-widget';
137
+
138
+ // 1. Configure — async; awaiting it ensures both clients are ready before use
139
+ await configure({
140
+ // REQUIRED — the well-known URL, shared by the journey and OIDC clients
141
+ serverConfig: {
142
+ wellknown:
143
+ 'https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
144
+ },
145
+ // REQUIRED if you use OAuth/OIDC tokens, user info, or logout
146
+ oidcClient: {
147
+ clientId: 'YourOauthClient',
148
+ redirectUri: `${window.location.origin}/callback`,
149
+ scope: 'openid profile email', // OPTIONAL — defaults to 'openid'
256
150
  },
257
151
  });
258
- ```
259
-
260
- NOTE: It's best to configure the Widget at the top level of your application, like its `index.js` or `app.js` file. This will ensure the Widget has the configuration needed to call out to the ForgeRock platform whenever you use the other Widget APIs.
261
152
 
262
- #### Starting a journey
263
-
264
- For the Widget to display the appropriate form fields, a request needs to be made to ForgeRock for the first step of the journey. To render the first step, you'll need to import the `journey` function and execute it to receive the `journeyEvents` object (you'll notice this is a pattern throughout the Widget API). Once you have this `journeyEvents` object, you can call the `journeyEvents.start` method. Calling `start` makes the initial request to the ForgeRock server for the initial form fields.
265
-
266
- ```js
267
- import Widget, { journey } from '@forgerock/login-widget';
153
+ // 2. Instantiate
154
+ const widgetRootEl = document.getElementById('widget-root');
155
+ new Widget({ target: widgetRootEl });
268
156
 
157
+ // 3. Start a journey
269
158
  const journeyEvents = journey();
270
-
271
- // ...
272
-
273
- new Widget({
274
- target: widgetRootEl, // Any existing element in the DOM
275
- });
276
-
277
- // Ensure you call `.start` *AFTER* instantiating the Widget
278
159
  journeyEvents.start();
279
-
280
- // OR, in a more typical situation, call on button click
281
- buttonElement.addEventListener('click', (event) => {
282
- journeyEvents.start();
283
- });
284
- ```
285
-
286
- NOTE: This `journeyEvents.start` method can be called anywhere in your application, or anytime, as long as it's _after_ calling the configuration's `set` method and _after_ instantiating the Widget (which mounts it to the DOM) as both are requirements for a journey.
287
-
288
- #### Getting a session
289
-
290
- By default, the Widget is going to do more than just get a user's session. It will also get OAuth/OIDC tokens and retrieve user information. But, let's start small.
291
-
292
- Let's only ask for session information, and disable OAuth and user info. This will simplify our initial setup. To do this, call the `journey` function passing false for `oauth` and `user`.
293
-
294
- ```js
295
- const journeyEvents = journey({ oauth: false, user: false });
296
160
  ```
297
161
 
298
- Now, when you call `journeyEvents.start()`, you will only get session information upon successful authentication.
299
-
300
- NOTE: It's also worth nothing that if you don't declare what journey you want to use, the ForgeRock platform will use what is marked as the default journey, usually just the basic Login journey.
301
-
302
- #### Listening for journey completion
303
-
304
- Use the `journeyEvents.subscribe` method (this is the observable part) to know when a user has completed their journey. Pass a callback function into this method to run on journey related events (there will be a quite a few of them). You will receive an event object with a lot of data in it. You'll want to conditionally check for the events you're interested in and ignore what you don't need.
305
-
306
- ```js
307
- // ...
308
-
309
- journeyEvents.subscribe((event) => {
310
- // Will be called multiple times, so narrowing what you're interested in is important
311
- if (event.journey.successful) {
312
- // Will log once to the console when authentication has succeeded and returned session data
313
- console.log(event);
314
- }
315
- });
316
- ```
162
+ > **Tip**: `configure()` is async — always `await` it at the top level of your application (`index.js` or `app.js`) before calling `journey().start()` or any other Widget API. This ensures both the OIDC and journey clients are fully constructed before any fetch is attempted.
317
163
 
318
- And, that's it. You now can mount, display, and authenticate users through the ForgeRock Login Widget. There are addition features documented below for a more complete implementation.
164
+ ## Observables Pattern
319
165
 
320
- ### Want to inline the Widget into your app (no modal)?
166
+ Most Widget APIs are asynchronous and use an event-centric observable pattern. The widget uses Svelte's simplified, standard observable implementation called a "store" — these are embedded in the widget and are not a dependency your app needs to manage.
321
167
 
322
- The Widget requires a real DOM element on which to mount. Since the inline type will be mounted within your application's controlled DOM, it's important to understand the lifecycle of how your framework mounts elements to the DOM.
168
+ [Read more about the Svelte store contract](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract).
323
169
 
324
- React, for example, uses the Virtual DOM, and the inline component cannot mount to a Virtual DOM element. So, you will need to wait until the element has been property mounted to the real DOM before instantiating the Widget.
325
-
326
- #### Instantiate the Widget (Inline)
327
-
328
- Now, import the Widget where you'd like to mount it. In whatever way your framework requires, provide a reference to the element mounted in the actual DOM as the target of the Widget instantiation.
329
-
330
- ```js
331
- // As inline
332
- import Widget from '@forgerock/login-widget';
333
-
334
- // ...
335
-
336
- new Widget({
337
- target: mountedDomElement, // ensure this is a reference to a real DOM element
338
- props: {
339
- type: 'inline', // Your JS SDK configuration; see below
340
- },
341
- });
342
- ```
343
-
344
- This mounts your Widget into the DOM. If you choose the modal version, it will be hidden at first.
345
-
346
- Note: [See additional documentation about configuring the JS SDK](https://backstage.forgerock.com/docs/sdks/3.3/javascript/configuring/configuring-forgerock-sdk-settings-for-your-javascript-app.html).
347
-
348
- ## What you need to know
349
-
350
- Most of this Widget's APIs are asynchronous. As with many things in the JavaScript world, there are multiple patterns to handle asynchronous behavior. We've decided to centralize the Widget's APIs around the event-centric, Observable pattern. Since this Widget is powered by Svelte's compiler, we use Svelte's simplified, but standard Observable implementation called a "store". You can [read more about the Svelte store contract in their docs](https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract).
351
-
352
- We believe this is an optimal pattern for UI development as it allows for a more dynamic, user experience. Your application will be updated with each event from emitted from within the Widget. These events could be "loading", "completed", "success" or "failure". To help illustrate this, let's take a look at one of the `user` APIs:
353
-
354
- ### Import the API object
170
+ ### Subscribe to Events
355
171
 
356
172
  ```js
357
173
  import { user } from '@forgerock/login-widget';
358
- ```
359
-
360
- ### Call API method to initiate observable
361
174
 
362
- Create a variable and assign the created observable:
363
-
364
- ```js
365
175
  const userInfoEvents = user.info();
366
- ```
367
176
 
368
- ### Use the subscribe method
369
-
370
- It's important to note that an observable is a "stream" of events over time. This means that the callback function in a subscribe will be called for each and every event until you unsubscribe from it (more on this later).
371
-
372
- ```js
373
177
  userInfoEvents.subscribe((event) => {
374
178
  if (event.loading) {
375
179
  console.log('User info is being requested from server');
376
- } else if (event.success) {
180
+ } else if (event.successful) {
377
181
  console.log('User info request was successful');
378
182
  console.log(event.response);
379
183
  } else if (event.error) {
@@ -383,432 +187,458 @@ userInfoEvents.subscribe((event) => {
383
187
  });
384
188
  ```
385
189
 
386
- ### Unsubscribing from an Observable
190
+ ### Unsubscribe
387
191
 
388
- Observables are not like a Promise in that Observables don't resolve and get "torn-down" after completion. Observables need to be unsubscribed from if they are no longer needed. This is especially important if you are subscribing to Observables in a component that gets created and destroyed many times over. Subscribing to an Observable over and over without unsubscribing will create a memory leak.
389
-
390
- To unsubscribe, you assign a function that is returned from calling the subscribe method to a variable that can be called at a later time.
192
+ Observables are not like Promises they don't resolve and get cleaned up. You must unsubscribe when no longer needed, especially in components that are created and destroyed frequently:
391
193
 
392
194
  ```js
393
- const unsubUserInfoEvents = userInfoEvents.subscribe((event) => console.log(event));
394
-
395
- // ...
195
+ const unsubscribe = userInfoEvents.subscribe((event) => console.log(event));
396
196
 
397
- // Unsubscribe when no longer needed
398
- unsubUserInfoEvents();
197
+ // Later, when no longer needed:
198
+ unsubscribe();
399
199
  ```
400
200
 
401
- NOTE: If you're subscribing at a top-level component in your app that's initiated once and is retained over the lifetime of your application, then unsubscribing is not needed. A good example of this would be your app's central, state management component/module. This is a perfect place to `subscribe` to an Observable and preserve that subscription.
201
+ > **Note**: If subscribing at a top-level component that persists over the lifetime of your app, unsubscribing is not needed.
402
202
 
403
- ### Getting the current, local value
203
+ ### Get Current Values
404
204
 
405
- Sometimes you just want the current value stored within the Widget and are not interested in future events and their resulting state changes. To do this, you can call `subscribe` and then immediately call the unsubscribe method.
205
+ To get the current value stored within the Widget without subscribing to future events, call `subscribe` and immediately call the returned unsubscribe function:
406
206
 
407
207
  ```js
408
- // Create variable for user info
409
208
  let userInfo;
410
- // Call subscribe, grab the current, local value, and then immediately call the returned function
411
- userInfoEvents.subscribe((event) => (userinfo = event.response))(); // <-- notice the second pair of parentheses
209
+ userInfoEvents.subscribe((event) => (userInfo = event.response))();
412
210
  ```
413
211
 
414
- What does "current, local value" mean? Good question. The Widget internally stores a lot of these important values, so you can ask the Widget for the values that it already has stored.
415
-
416
- ### Requesting values from the ForgeRock platform
212
+ ### Request Fresh Values
417
213
 
418
- You can ask the Widget to request new, fresh values from the ForgeRock server, rather than just what it has stored locally. This is done by calling the Observables action methods, like `get`.
214
+ Call action methods like `get` to request new values from the server. Any active `subscribe` callbacks will receive the resulting events:
419
215
 
420
216
  ```js
421
217
  userInfoEvents.get();
422
218
  ```
423
219
 
424
- If you're using the Observable pattern, you can call this method and forget about it. The `subscribe` callback function you have for this Observable will receive the events and new state from this `get` call. The `subscribe` can exist before or after this `get` call, and it will still capture the resulting events.
220
+ ### Use Promises
425
221
 
426
- ### Using Promises
427
-
428
- We recommend Observables, but the choice is up to you. All of the Widget's APIs that involve network calls have an alternative Promise implementation that can be used. Let's take the `get` method on `userInfoEvents` we saw above as a way to fetch new user info, and convert it into a Promise.
222
+ All Widget APIs that involve network calls also support Promises:
429
223
 
430
224
  ```js
431
- // async-await
432
- let userInfo;
433
- async function example() {
434
- try {
435
- userInfo = await userInfoEvents.get();
436
- } catch (err) {
437
- console.log(err);
438
- }
439
- }
225
+ // async/await
226
+ const userInfo = await userInfoEvents.get();
440
227
 
441
- // Promise
442
- let userInfo;
228
+ // Promise chain
443
229
  userInfoEvents
444
230
  .get()
445
- .then((data) => (userInfo = data))
446
- .catch((err) => console.log(err));
231
+ .then((data) => console.log(data))
232
+ .catch((err) => console.error(err));
447
233
  ```
448
234
 
449
235
  ## Complete Widget API
450
236
 
451
- The Widget comes with methods and event handlers used to control the lifecycle of user journeys/authentication.
452
-
453
237
  ### Widget
454
238
 
455
239
  ```js
456
240
  import Widget from '@forgerock/login-widget';
457
241
 
458
- // Instantiate Widget
242
+ // Instantiate
459
243
  const widget = new Widget({
460
- target: widgetRootEl, // REQUIRED; Element mounted in DOM
244
+ target: widgetRootEl, // REQUIRED; a real DOM element
461
245
  props: {
462
- type: 'modal', // OPTIONAL; "modal" or "inline"; "modal" is default
246
+ type: 'modal', // OPTIONAL; "modal" (default) or "inline"
463
247
  },
464
248
  });
465
249
 
466
- // OPTIONAL; Remove widget from DOM and destroy component listeners
250
+ // Remove widget from DOM and destroy listeners
467
251
  widget.$destroy();
468
252
  ```
469
253
 
470
- NOTE: Calling `$destroy()` is important if the instantiation of the Widget is done within a portion of your application that is frequently created and destroyed. Though, we _strongly_ encourage developers to instantiate the Widget higher up in the application code closer to the top-level file, in a component that is created once and preserved.
254
+ > **Note**: Call `$destroy()` if instantiating the Widget in a component that is frequently created and destroyed. We recommend instantiating higher in your application tree, in a component created once and preserved.
471
255
 
472
256
  ### Configuration
473
257
 
474
- This configuration function produces a config API, and its `set` method is required for the underlying JavaScript SDK to interaction with the ForgeRock platform or access stored tokens.
475
-
476
258
  ```js
477
- import { configuration } from '@forgerock/login-widget';
478
-
479
- const config = configuration();
480
- config.set({
481
- forgerock: {
482
- /**
483
- * REQUIRED; SDK configuration object
484
- */
485
- serverConfig: {
486
- baseUrl: 'https://customer.forgeblocks.com/am',
487
- timeout: 3000, // Number (in milliseconds); 3 to 5 seconds should be fine
488
- },
489
- /**
490
- * OPTIONAL, *BUT ENCOURAGED*, CONFIGURATION
491
- * Remaining config is optional with fallback values shown
492
- */
493
- clientId: 'WebLoginWidgetClient', // String; defaults to 'WebLoginWidgetClient'
494
- realmPath: 'alpha', // String; defaults to 'alpha'
495
- redirectUri: window.location.href, // URL string; defaults to `window.location.href`
496
- scope: 'openid email', // String; defaults to 'openid email'
497
- /**
498
- * NOT RECOMMENDED
499
- * Rather, configure a journey/tree through the `.start({ journey: 'Login' })` options object,
500
- * avoid setting it here
501
- */
502
- tree: 'Login', // String, but NOT recommended.
259
+ import { configure } from '@forgerock/login-widget';
260
+
261
+ // configure() is async — await it before calling any other Widget API
262
+ await configure({
263
+ // REQUIRED — the well-known URL, shared by the journey and OIDC clients
264
+ serverConfig: {
265
+ wellknown:
266
+ 'https://your-tenant.forgeblocks.com/am/oauth2/realms/root/realms/alpha/.well-known/openid-configuration',
267
+ },
268
+ // REQUIRED if you use OAuth/OIDC tokens, user info, or logout
269
+ oidcClient: {
270
+ clientId: 'WebOAuthClient',
271
+ redirectUri: `${window.location.origin}/callback`,
272
+ scope: 'openid profile email',
503
273
  },
504
- /**
505
- * OPTIONAL; See below for the Content Configuration section
506
- */
274
+ // OPTIONAL — logger for both clients; `level` gates verbosity and `custom`
275
+ // redirects SDK log output to your own sink. See the full example below.
276
+ logger: { level: 'warn' },
277
+ // OPTIONAL — request middleware for both clients; see the full example below
278
+ middleware: [],
279
+ // OPTIONAL — token storage config; see Storage section below
280
+ storage: { type: 'sessionStorage', name: 'tokens' },
281
+ // OPTIONAL — see dedicated sections below
507
282
  content: {},
508
- /**
509
- * OPTIONAL; See below for the Links Configuration section
510
- */
511
283
  links: {},
512
- /**
513
- * OPTIONAL; See below for Styling Configuration section
514
- */
515
284
  style: {},
516
285
  });
517
286
  ```
518
287
 
519
- NOTE: For more SDK configuration options, please [see our SDK's configuration document](https://backstage.forgerock.com/docs/sdks/3.3/javascript/configuring/configuring-forgerock-sdk-settings-for-your-javascript-app.html), or you can [see our API docs for more developer detail](https://backstage.forgerock.com/docs/sdks/3.3/_attachments/javascript/api-reference-core/interfaces/configoptions.html).
288
+ > **Migration note (2.0.0):** The `forgerock` config object has been replaced by `oidcClient`.
289
+ > Endpoint discovery is now driven by `serverConfig.wellknown`, shared by the journey and OIDC
290
+ > clients — `baseUrl`, `realmPath`, `timeout`, and `support` are no longer used. `clientId` and
291
+ > `redirectUri` are required when configuring `oidcClient`; `scope` defaults to `'openid'`. `tokenStore` has
292
+ > moved to a top-level `storage` option.
520
293
 
521
- ### Journey
294
+ #### Logger
295
+
296
+ The top-level `logger` option is forwarded to both the journey and OIDC clients.
522
297
 
523
- The `journey` object:
298
+ | Property | Type | Default | Description |
299
+ | -------- | -------------------------------------------------- | --------- | --------------------------------------------------------------------------------------- |
300
+ | `level` | `'none' \| 'error' \| 'warn' \| 'info' \| 'debug'` | `'error'` | Gates SDK log verbosity. `'none'` silences all SDK logs. |
301
+ | `custom` | `{ error, warn, info, debug }` | — | Sink for SDK log output. When set, the SDK calls your methods instead of the `console`. |
302
+
303
+ ```js
304
+ await configure({
305
+ serverConfig: {
306
+ wellknown:
307
+ 'https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
308
+ },
309
+ logger: {
310
+ level: 'debug',
311
+ // OPTIONAL — route SDK logs to your own sink instead of the console.
312
+ custom: {
313
+ error: (...args) => myLogger.error(...args),
314
+ warn: (...args) => myLogger.warn(...args),
315
+ info: (...args) => myLogger.info(...args),
316
+ debug: (...args) => myLogger.debug(...args),
317
+ },
318
+ },
319
+ oidcClient: {
320
+ clientId: 'WebOAuthClient',
321
+ redirectUri: `${window.location.origin}/callback`,
322
+ scope: 'openid profile email',
323
+ },
324
+ });
325
+ ```
326
+
327
+ #### OIDC Client Options
328
+
329
+ All properties are nested inside `oidcClient`.
330
+
331
+ | Property | Type | Default | Description |
332
+ | ---------------- | ------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
333
+ | `clientId` | `string` | — | **Required.** OAuth 2.0 client ID. |
334
+ | `redirectUri` | `string` | — | **Required.** URI AM redirects to after authorization. |
335
+ | `scope` | `string` | `'openid'` | OAuth 2.0 scopes. |
336
+ | `oauthThreshold` | `number` | `30000` | Milliseconds before expiry to trigger background renewal. |
337
+ | `par` | `boolean` | auto | Use Pushed Authorization Requests. When omitted, the SDK auto-detects from the authorization server's `require_pushed_authorization_requests` metadata. Setting `false` while the server requires PAR is an error. |
338
+ | `loginHint` | `string` | — | Pre-fills the login identifier; bridged onto silent token renewal. |
339
+ | `acrValues` | `string` | — | Requested ACR values; bridged onto silent token renewal. |
340
+ | `query` | `Record<string, string>` | — | Extra authorize query params; bridged onto silent token renewal. |
341
+
342
+ Example with all optional OIDC options:
343
+
344
+ ```js
345
+ await configure({
346
+ serverConfig: {
347
+ wellknown:
348
+ 'https://your-tenant.forgeblocks.com/am/oauth2/alpha/.well-known/openid-configuration',
349
+ },
350
+ logger: { level: 'debug' },
351
+ middleware: [
352
+ (req, action, next) => {
353
+ console.log('[middleware]', action.type, req.url);
354
+ next();
355
+ },
356
+ ],
357
+ storage: { type: 'sessionStorage', name: 'tokens', prefix: 'myapp' },
358
+ oidcClient: {
359
+ clientId: 'WebOAuthClient',
360
+ redirectUri: `${window.location.origin}/callback`,
361
+ scope: 'openid profile email',
362
+ oauthThreshold: 60000,
363
+ par: true,
364
+ loginHint: 'user@example.com',
365
+ acrValues: 'urn:acr:2fa',
366
+ query: { ui_locales: 'en-US' },
367
+ },
368
+ });
369
+ ```
370
+
371
+ #### Storage
372
+
373
+ The top-level `storage` option configures where the OIDC client persists tokens. It mirrors
374
+ the SDK's `StorageConfig` union — `type` selects a browser store or a custom sink.
375
+
376
+ | Property | Type | Default | Description |
377
+ | -------- | ------------------------------------------------ | ---------------- | ----------------------------------------------------------------------- |
378
+ | `type` | `'localStorage' \| 'sessionStorage' \| 'custom'` | `'localStorage'` | Storage backend. `'custom'` requires a `custom` sink. |
379
+ | `name` | `string` | — | **Required.** Storage key name. |
380
+ | `prefix` | `string` | `'pic'` | Key prefix for storage entries. |
381
+ | `custom` | `{ get, set, remove }` | — | **Required when `type: 'custom'`.** Async functions for your own store. |
382
+
383
+ ```js
384
+ // Browser store
385
+ storage: { type: 'sessionStorage', name: 'tokens', prefix: 'myapp' },
386
+
387
+ // Custom store
388
+ storage: {
389
+ type: 'custom',
390
+ name: 'tokens',
391
+ custom: {
392
+ get: async (key) => myStore.read(key),
393
+ set: async (key, value) => myStore.write(key, value),
394
+ remove: async (key) => myStore.delete(key),
395
+ },
396
+ },
397
+ ```
398
+
399
+ ### Journey
524
400
 
525
401
  ```js
526
402
  import { journey } from '@forgerock/login-widget';
527
403
 
528
- // Call to start the journey
529
- // Optional config can be passed in, see below for more details
530
404
  const journeyEvents = journey({
531
- oauth: true, // OPTIONAL; defaults to true; uses OAuth flow for acquiring tokens
532
- user: true, // OPTIONAL; default to true; returns user information from `userinfo` endpoint
405
+ oauth: true, // OPTIONAL; default true; use OAuth flow for tokens
406
+ user: true, // OPTIONAL; default true; fetch user info from /userinfo
533
407
  });
534
408
 
535
409
  // Start a journey
536
410
  journeyEvents.start({
537
- forgerock: {}, // OPTIONAL; configuration overrides
538
- journey: 'Login', // OPTIONAL; choice the journey or tree you want to start
539
- resumeUrl: window.location.href, // OPTIONAL; the full URL for resuming a tree (see resuming a journey section)
411
+ journey: 'Login', // OPTIONAL; journey name (omit to let AM choose the default)
412
+ resumeUrl: window.location.href, // OPTIONAL; URL for resuming a suspended journey
540
413
  });
541
414
 
542
- // Change a journey
415
+ // Change to a different journey
543
416
  journeyEvents.change({
544
- forgerock: {}, // OPTIONAL; configuration overrides
545
- journey: 'Registration', // OPTIONAL; choice the journey or tree you want to start
417
+ journey: 'Registration',
546
418
  });
547
419
 
548
- // Listeners for journey events
549
- // See below for more details on `event`
550
- journey.subscribe((event) => {
551
- /* Run anything you want */
420
+ // Listen for events
421
+ journeyEvents.subscribe((event) => {
422
+ if (event.journey.successful) {
423
+ console.log('Authentication succeeded', event);
424
+ }
552
425
  });
553
426
  ```
554
427
 
555
- Schema for user `event`:
428
+ **Journey event schema:**
556
429
 
557
430
  ```js
558
- // response
559
431
  {
560
432
  journey: {
561
- completed: false, // boolean
562
- error: null, // null or object with `code`, `message` and `step` that failed
563
- loading: false, // boolean
564
- step: null, // null or object with the last step object from ForgeRock AM
565
- successful: false, // boolean
566
- response: null, // null or object, if successful, it will contain the success response from AM
433
+ completed: false, // boolean
434
+ error: null, // null or { code, message, step }
435
+ loading: false, // boolean
436
+ step: null, // null or step object from ForgeRock AM
437
+ successful: false, // boolean
438
+ response: null, // null or success response from AM
567
439
  },
568
440
  oauth: {
569
- completed: false, // boolean
570
- error: null, // null or object with `code` and `message` properties
571
- loading: false, // boolean
572
- successful: false, // boolean
573
- response: null, // null or object with OAuth/OIDC tokens
441
+ completed: false, // boolean
442
+ error: null, // null or { code, message }
443
+ loading: false, // boolean
444
+ successful: false, // boolean
445
+ response: null, // null or OAuth/OIDC tokens
574
446
  },
575
447
  user: {
576
- completed: false, // boolean
577
- error: null, // null or object with `code` and `message` properties
578
- loading: false, // boolean
579
- successful: false, // boolean
580
- response: null, // null or object with user information driven by OAuth scope config
448
+ completed: false, // boolean
449
+ error: null, // null or { code, message }
450
+ loading: false, // boolean
451
+ successful: false, // boolean
452
+ response: null, // null or user info (driven by OAuth scope)
581
453
  },
582
454
  }
583
455
  ```
584
456
 
585
457
  ### Component
586
458
 
587
- The named `component` import provides methods for both listening (modal and inline type) as well as controlling (modal type only) the widget component. After initializing the component API via `component()`, you will receive an observable. Subscribing to this observable will allow you to listen and react to the state of the component.
459
+ The `component` API controls the widget's lifecycle. For the modal form factor, it provides `open` and `close` methods.
588
460
 
589
461
  ```js
590
462
  import { component } from '@forgerock/login-widget';
591
463
 
592
- // Initiate the component API
593
464
  const componentEvents = component();
594
465
 
595
- // Know when the component, both modal and inline has been mounted.
596
- // When using the modal type, you will also receive open and close events.
597
- // The property `reason` will be either "auto", "external", or "user" (see below)
598
- componentEvents.subscribe((event) => {
599
- /* Run anything you want */
600
- });
601
-
602
- // Open the modal
466
+ // Open/close the modal
603
467
  componentEvents.open();
604
-
605
- // Close the modal
606
468
  componentEvents.close();
469
+
470
+ // Listen for lifecycle events
471
+ componentEvents.subscribe((event) => {
472
+ if (event.mounted) console.log('Widget is mounted');
473
+ if (event.open === false) console.log('Modal closed:', event.reason);
474
+ });
607
475
  ```
608
476
 
609
- Schema for component `event`:
477
+ **Component event schema:**
610
478
 
611
479
  ```js
612
480
  {
613
- error: null, // null or object with `code`, `message` and `step` that failed
481
+ error: null, // null or { code, message, step }
614
482
  mounted: false, // boolean
615
- open: null, // boolean or null, depending on the Widget type: e.g. "modal" or "inline"
616
- reason: null, // string to describe the reason for the event
617
- type: null, // 'modal' or 'inline'
483
+ open: null, // boolean or null (null for inline type)
484
+ reason: null, // "user" | "auto" | "external"
485
+ type: null, // "modal" | "inline"
618
486
  }
619
487
  ```
620
488
 
621
- The `reason` value is used for communicating why the modal has closed. The below are the potential values:
489
+ **Close reasons:**
622
490
 
623
- 1. `"user"`: user closed the dialog via UI
624
- 2. `"auto"`: the modal was closed because user successfully authenticated
625
- 3. `"external"`: the application itself called the `modal.close` function
491
+ | Reason | Description |
492
+ | ------------ | -------------------------------------------- |
493
+ | `"user"` | User closed the dialog via UI |
494
+ | `"auto"` | Modal closed after successful authentication |
495
+ | `"external"` | Application called `componentEvents.close()` |
626
496
 
627
497
  ### User
628
498
 
629
499
  ```js
630
500
  import { user } from '@forgerock/login-widget';
631
501
 
632
- /**
633
- * User info API
634
- */
502
+ // User info
635
503
  const userEvents = user.info();
636
- // Subscribe to user info changes
637
- userEvents.subscribe((event) => {
638
- // Will return current, *local*, user info and future state changes
639
- console.log(event);
640
- });
641
- // Fetch/get fresh user info from ForgeRock server
642
- userEvents.get(); // New state will be returned in your `userEvents.subscribe` callback function
504
+ userEvents.subscribe((event) => console.log(event));
505
+ userEvents.get(); // Fetch fresh user info from server
643
506
 
644
- /**
645
- * User tokens API
646
- */
507
+ // User tokens
647
508
  const tokenEvents = user.tokens();
648
- // Subscribe to user info changes
649
- tokenEvents.subscribe((event) => {
650
- // Will return current, *local*, user tokens and future state changes
651
- console.log(event);
652
- });
653
- // Fetch/get fresh user tokens from ForgeRock server
654
- tokenEvents.get(); // New state will be returned in your tokenEvents.subscribe` callback function
655
-
656
- /**
657
- * Logout
658
- * Log user out and clear user data (info and tokens)
659
- */
660
- user.logout(); // Resets user and emits event to your info and tokens' `.subscribe` callback function
661
- ```
662
-
663
- Schema for user info `event`:
509
+ tokenEvents.subscribe((event) => console.log(event));
510
+ tokenEvents.get(); // Fetch fresh tokens from server
664
511
 
665
- ```js
666
- {
667
- completed: false, // boolean
668
- error: null, // null or object with `code`, `message` and `step` that failed
669
- loading: false, // boolean
670
- successful: false, // boolean
671
- response: null, // object returned from the `/userinfo` endpoint configured in ForgeRock
672
- }
512
+ // Logout
513
+ user.logout(); // Clears user data and emits events to subscribers
673
514
  ```
674
515
 
675
- Schema for token `event`:
516
+ **User info / token event schema:**
676
517
 
677
518
  ```js
678
519
  {
679
- completed: false, // boolean
680
- error: null, // null or object with `code`, `message` and `step` that failed
681
- loading: false, // boolean
520
+ completed: false, // boolean
521
+ error: null, // null or { code, message }
522
+ loading: false, // boolean
682
523
  successful: false, // boolean
683
- response: null, // object returned from the `/access_token` endpoint configured in ForgeRock
524
+ response: null, // object from /userinfo or /access_token endpoint
684
525
  }
685
526
  ```
686
527
 
687
- ### Request
688
-
689
- The Widget has an alias to the JavaScript SDK's `HttpClient.request`, which is a convenience wrapper around the native `fetch`. All this does is auto-inject the Access Token into the `Authorization` header and manage some of the lifecycle around the token.
528
+ ### Calling protected resources
690
529
 
691
- NOTE: This request function is just a wrapper around the native `fetch` API. It's purely promise based and the [response object is the native `Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response). The response is not persisted locally within the Widget.
530
+ > **Removed in 2.0.0:** The `request` export (an alias to the legacy `HttpClient.request`) has been
531
+ > removed. `@forgerock/oidc-client` does not provide an HTTP client. Get the access token from
532
+ > `user.tokens()` and call `fetch` directly, adding the `Authorization` header yourself:
692
533
 
693
534
  ```js
694
- import Widget from '@forgerock/login-widget';
695
-
696
- // See below for more details on the options
697
- const response = await request({ init: { method: 'GET' }, url: 'https://protected.resource.com' });
698
- ```
535
+ import { user } from '@forgerock/login-widget';
699
536
 
700
- The full `options` object:
537
+ const tokenEvents = user.tokens();
538
+ const { response: tokens } = await tokenEvents.get();
701
539
 
702
- ```js
703
- {
704
- bypassAuthentication: false, // Boolean; if true, Access Token is not injected into Authorization header
705
- init: {
706
- // Options object for `fetch` API: https://developer.mozilla.org/en-US/docs/Web/API/fetch
540
+ const response = await fetch('https://protected.resource.com', {
541
+ method: 'GET',
542
+ headers: {
543
+ Authorization: `Bearer ${tokens.accessToken}`,
707
544
  },
708
- timeout: 3000, // Fetch timeout in milliseconds
709
- url: 'https://protected.resource.com', // String; the URL of the resource
710
-
711
- // Unsupported properties
712
- authorization: {},
713
- requiresNewToken: () => {},
714
- }
545
+ });
715
546
  ```
716
547
 
717
- For the full type definition of this, please [view our SDK API documentation](https://backstage.forgerock.com/docs/sdks/3.3/_attachments/javascript/api-reference-core/interfaces/httpclientrequestoptions.html).
548
+ > **Note**: The legacy `request` automatically refreshed tokens on a 401 and parsed Identity Gateway
549
+ > policy advice. Those behaviors are not provided by the new SDK and must be implemented by the consumer if needed.
718
550
 
719
551
  ### Styling Configuration
720
552
 
721
- The Widget can be configured for styling purposes via the JavaScript API. This allows you to choose the type of labels used or providing a logo for the modal.
722
-
723
- Example:
553
+ Configure the widget's visual appearance:
724
554
 
725
555
  ```js
726
- const config = configuration();
727
-
728
- config.set({
556
+ await configure({
729
557
  style: {
730
- checksAndRadios: 'animated', // OPTIONAL; choices are 'animated' or 'standard'
731
- labels: 'floating', // OPTIONAL; choices are 'floating' or 'stacked'
558
+ checksAndRadios: 'animated', // OPTIONAL; 'animated' or 'standard'
559
+ labels: 'floating', // OPTIONAL; 'floating' or 'stacked'
732
560
  logo: {
733
- // OPTIONAL; only used with modal form factor
734
- dark: 'https://example.com/img/white-logo.png', // OPTIONAL; used if theme has a dark variant
735
- light: 'https://example.com/img/black-logo.png', // REQUIRED if logo property is provided; full URL
736
- height: 300, // OPTIONAL; number of pixels for providing additional controls to logo display
737
- width: 400, // OPTIONAL; number of pixels for providing additional controls to logo display
561
+ // OPTIONAL; modal only
562
+ dark: 'https://example.com/img/white-logo.png',
563
+ light: 'https://example.com/img/black-logo.png', // REQUIRED if logo provided
564
+ height: 300,
565
+ width: 400,
738
566
  },
739
567
  sections: {
740
- // OPTIONAL; only used with modal form factor
741
- header: false, // OPTIONAL; uses a modal "header" section that displays logo
568
+ // OPTIONAL; modal only
569
+ header: false, // Display a header section with logo
742
570
  },
743
571
  stage: {
744
- icon: true, // OPTIONAL; displays generic icons for the provided stages
572
+ icon: true, // OPTIONAL; display generic stage icons
745
573
  },
746
574
  },
747
575
  });
748
576
  ```
749
577
 
750
- Note that the `logo` and `section` property only apply to the "modal" form factor, and not the "inline".
578
+ > **Note**: The `logo` and `sections` properties only apply to the modal form factor.
751
579
 
752
580
  ### Links Configuration
753
581
 
754
- Use this configuration option to set the URL for your site or app's Terms & Conditions page. This supports the the `TermsAndConditionsCallback` that's commonly found in a registration flow.
755
-
756
- Example:
582
+ Set the URL for your Terms & Conditions page (used by `TermsAndConditionsCallback`):
757
583
 
758
584
  ```js
759
- const config = configuration();
760
-
761
- config.set({
585
+ await configure({
762
586
  links: {
763
- termsAndConditions: 'https://example.com/terms', // Full canonical URL for your app's terms and conditions page
587
+ termsAndConditions: 'https://example.com/terms',
764
588
  },
765
589
  });
766
590
  ```
767
591
 
768
592
  ### Content Configuration
769
593
 
770
- This configuration setting is for passing custom content to the Widget, replacing its default content. For the full content schema, please [use the example en-US locale file](/src/locales/us/en/index.ts).
771
-
772
- Example:
594
+ Override the widget's default content with custom text. For the full schema, see the [en-US locale file](https://github.com/ForgeRock/forgerock-web-login-framework/tree/main/core/locales).
773
595
 
774
596
  ```js
775
- const config = configuration();
776
-
777
- config.set({
778
- content: {}, // Custom content that overrides Widget default content
597
+ await configure({
598
+ content: {
599
+ // Custom content that overrides Widget defaults
600
+ },
779
601
  });
780
602
  ```
781
603
 
782
- ## Future Support (not yet implemented)
604
+ ### CAPTCHA Configuration
605
+
606
+ AM does not signal invisible mode in the callback payload for either `ReCaptchaCallback` or `ReCaptchaEnterpriseCallback`. Use the `captcha` option to configure invisible rendering:
607
+
608
+ ```js
609
+ await configure({
610
+ captcha: {
611
+ mode: 'invisible', // 'visible' (default) | 'invisible'
612
+ },
613
+ });
614
+ ```
783
615
 
784
- ### Planned for a future, minor release
616
+ **Script loading:** The widget automatically injects the required CAPTCHA script at mount time — no manual `<script>` tag is needed. If the provider API (`window.grecaptcha` / `window.hcaptcha`) is already present on the page when the widget mounts, injection is skipped.
785
617
 
786
- 1. WebAuthn
787
- 2. Device Profile
618
+ ## Supported Callbacks
788
619
 
789
- ### Planned for a future, major release
620
+ The widget supports the following ForgeRock callbacks:
790
621
 
791
- 1. Push Authentication
792
- 2. ReCAPTCHA
793
- 3. QR Code display
794
- 4. TextOutputCallback with scripts
795
- 5. Central Login
796
- 6. SAML
797
- 7. NumberAttributeInputCallback
622
+ - Page node
623
+ - Username, Password
624
+ - WebAuthn (registration and login)
625
+ - Push authentication
626
+ - One-time password verification
627
+ - Social login (Apple, Facebook, Google)
628
+ - Email suspend ("magic links")
629
+ - Device profile
630
+ - reCAPTCHA v2 (visible + invisible), reCAPTCHA Enterprise, hCaptcha (visible + invisible)
631
+ - QR codes
632
+ - Ping Protect
798
633
 
799
634
  ## Disclaimer
800
635
 
801
- > **This code is provided by ForgeRock on an as is basis, without warranty of any kind, to the fullest extent permitted by law. ForgeRock does not represent or warrant or make any guarantee regarding the use of this code or the accuracy, timeliness or completeness of any data or information relating to this code, and ForgeRock hereby disclaims all warranties whether express, or implied or statutory, including without limitation the implied warranties of merchantability, fitness for a particular purpose, and any warranty of non-infringement. ForgeRock shall not have any liability arising out of or related to any use, implementation or configuration of this code, including but not limited to use for any commercial purpose. Any action or suit relating to the use of the code may be brought only in the courts of a jurisdiction wherein ForgeRock resides or in which ForgeRock conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions.**
802
-
803
- <!---------------------------------------------------------------------------------------------------------->
804
- <!-- LICENSE - Links to the MIT LICENSE file in each repo. -->
636
+ > **This code is provided by ForgeRock on an "as is" basis, without warranty of any kind, to the fullest extent permitted by law. ForgeRock does not represent or warrant or make any guarantee regarding the use of this code or the accuracy, timeliness or completeness of any data or information relating to this code, and ForgeRock hereby disclaims all warranties whether express, or implied or statutory, including without limitation the implied warranties of merchantability, fitness for a particular purpose, and any warranty of non-infringement. ForgeRock shall not have any liability arising out of or related to any use, implementation or configuration of this code, including but not limited to use for any commercial purpose. Any action or suit relating to the use of the code may be brought only in the courts of a jurisdiction wherein ForgeRock resides or in which ForgeRock conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions.**
805
637
 
806
638
  ## License
807
639
 
808
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
640
+ This project is licensed under the MIT License see the [LICENSE](https://github.com/ForgeRock/forgerock-web-login-framework/blob/main/LICENSE) file for details.
809
641
 
810
642
  ---
811
643
 
812
- &copy; Copyright 2022-2023 ForgeRock AS. All Rights Reserved.
813
-
814
- [forgerock-logo]: https://www.forgerock.com/themes/custom/forgerock/images/fr-logo-horz-color.svg 'ForgeRock Logo'
644
+ &copy; Copyright 2022-2026 Ping Identity Corporation. All Rights Reserved.