|
| 1 | +# Migration Guide: Legacy JavaScript SDK to Ping SDK |
| 2 | + |
| 3 | +This guide documents the conversion from `@forgerock/javascript-sdk` (legacy) to the newer Ping SDK packages. For the complete API-level mapping of every class, method, parameter, and return type, see [`interface_mapping.md`](./interface_mapping.md). |
| 4 | + |
| 5 | +## Package Dependencies |
| 6 | + |
| 7 | +| Legacy | New | Purpose | |
| 8 | +| --------------------------- | --------------------------- | ------------------------------------------------------------------ | |
| 9 | +| `@forgerock/javascript-sdk` | `@forgerock/journey-client` | Authentication tree/journey flows | |
| 10 | +| `@forgerock/javascript-sdk` | `@forgerock/oidc-client` | OAuth2/OIDC token management, user info, logout | |
| 11 | +| `@forgerock/javascript-sdk` | `@forgerock/sdk-types` | Shared types and enums | |
| 12 | +| `@forgerock/javascript-sdk` | `@forgerock/device-client` | Device profile & management (OATH, Push, WebAuthn, Bound, Profile) | |
| 13 | +| `@forgerock/ping-protect` | `@forgerock/protect` | PingOne Protect/Signals integration | |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## SDK Initialization & Configuration |
| 18 | + |
| 19 | +The legacy SDK uses a global static `Config.set()`. The new SDK uses **async factory functions** that each return an independent client instance. |
| 20 | + |
| 21 | +| Legacy | New | Notes | |
| 22 | +| ----------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------ | |
| 23 | +| `import { Config } from '@forgerock/javascript-sdk'` | `import { journey } from '@forgerock/journey-client'` | Journey client factory | |
| 24 | +| — | `import { oidc } from '@forgerock/oidc-client'` | OIDC client factory (separate package) | |
| 25 | +| `Config.set({ clientId, redirectUri, scope, serverConfig, realmPath, tree })` | `const journeyClient = await journey({ config })` | Async initialization; config per-client, not global | |
| 26 | +| `TokenStorage.get()` | `const tokens = await oidcClient.token.get()` | Now part of oidcClient; check errors with `if ('error' in tokens)` | |
| 27 | + |
| 28 | +### Wellknown Configuration |
| 29 | + |
| 30 | +Both clients require only the OIDC wellknown endpoint URL. All other configuration (baseUrl, paths, realm) is derived automatically: |
| 31 | + |
| 32 | +```typescript |
| 33 | +import type { JourneyClientConfig } from '@forgerock/journey-client/types'; |
| 34 | + |
| 35 | +// Shared config — journey-client only needs serverConfig.wellknown |
| 36 | +const config: JourneyClientConfig = { |
| 37 | + serverConfig: { |
| 38 | + wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration', |
| 39 | + }, |
| 40 | +}; |
| 41 | + |
| 42 | +// Journey client — for authentication tree flows |
| 43 | +const journeyClient = await journey({ config }); |
| 44 | + |
| 45 | +// OIDC client — for token management, user info, logout |
| 46 | +const oidcClient = await oidc({ |
| 47 | + config: { |
| 48 | + ...config, |
| 49 | + clientId: 'my-app', |
| 50 | + redirectUri: `${window.location.origin}/callback`, |
| 51 | + scope: 'openid profile', |
| 52 | + }, |
| 53 | +}); |
| 54 | +``` |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## Authentication & Journey Flow |
| 59 | + |
| 60 | +| Legacy Method | New Method | Notes | |
| 61 | +| -------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------ | |
| 62 | +| `FRAuth.start({ tree: 'Login' })` | `journeyClient.start({ journey: 'Login' })` | Tree name passed per-call via `journey` param (not in config) | |
| 63 | +| `FRAuth.next(step, { tree: 'Login' })` | `journeyClient.next(step)` | Tree is set at `start()`, not repeated on `next()` | |
| 64 | +| `FRAuth.redirect(step)` | `await journeyClient.redirect(step)` | Now async. Step stored in `sessionStorage` (was `localStorage`) | |
| 65 | +| `FRAuth.resume(resumeUrl)` | `await journeyClient.resume(resumeUrl)` | Previous step retrieved from `sessionStorage` (was `localStorage`) | |
| 66 | +| No equivalent | `await journeyClient.terminate()` | **New.** Ends the AM session via `/sessions` endpoint | |
| 67 | + |
| 68 | +### Before/After: Login Flow |
| 69 | + |
| 70 | +**Legacy:** |
| 71 | + |
| 72 | +```typescript |
| 73 | +import { FRAuth, StepType } from '@forgerock/javascript-sdk'; |
| 74 | + |
| 75 | +Config.set({ |
| 76 | + serverConfig: { baseUrl: 'https://am.example.com/am' }, |
| 77 | + realmPath: 'alpha', |
| 78 | + tree: 'Login', |
| 79 | +}); |
| 80 | + |
| 81 | +let step = await FRAuth.start(); |
| 82 | +while (step.type === StepType.Step) { |
| 83 | + // ... handle callbacks ... |
| 84 | + step = await FRAuth.next(step); |
| 85 | +} |
| 86 | +if (step.type === StepType.LoginSuccess) { |
| 87 | + console.log('Session token:', step.getSessionToken()); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +**New:** |
| 92 | + |
| 93 | +```typescript |
| 94 | +import { journey } from '@forgerock/journey-client'; |
| 95 | + |
| 96 | +const journeyClient = await journey({ |
| 97 | + config: { |
| 98 | + serverConfig: { |
| 99 | + wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration', |
| 100 | + }, |
| 101 | + }, |
| 102 | +}); |
| 103 | + |
| 104 | +let result = await journeyClient.start({ journey: 'Login' }); |
| 105 | +while (result.type === 'Step') { |
| 106 | + // ... handle callbacks ... |
| 107 | + result = await journeyClient.next(result); |
| 108 | +} |
| 109 | +if ('error' in result) { |
| 110 | + console.error('Journey error:', result); |
| 111 | +} else if (result.type === 'LoginSuccess') { |
| 112 | + console.log('Session token:', result.getSessionToken()); |
| 113 | +} |
| 114 | +``` |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +## User Management |
| 119 | + |
| 120 | +| Legacy | New | Notes | |
| 121 | +| -------------------------------------- | -------------------------------- | -------------------------------------------------------- | |
| 122 | +| `UserManager.getCurrentUser()` | `await oidcClient.user.info()` | Returns typed `UserInfoResponse` or `GenericError` | |
| 123 | +| `FRUser.logout({ logoutRedirectUri })` | `await oidcClient.user.logout()` | Revokes tokens + ends session. Returns structured result | |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Token Management & OAuth Flow |
| 128 | + |
| 129 | +| Legacy | New | Notes | |
| 130 | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------ | |
| 131 | +| `TokenManager.getTokens({ forceRenew: true })` | `await oidcClient.token.get({ forceRenew: true, backgroundRenew: true })` | Single call with auto-renewal. Returns tokens or error | |
| 132 | +| `TokenManager.getTokens()` then manual code+state | `await oidcClient.authorize.background()` then `await oidcClient.token.exchange(code, state)` | Two-step when you need explicit control | |
| 133 | +| `TokenManager.deleteTokens()` | `await oidcClient.token.revoke()` | Revokes remotely AND deletes locally | |
| 134 | +| `TokenStorage.get()` | `await oidcClient.token.get()` | Auto-retrieves from storage; check `'error' in tokens` | |
| 135 | +| `TokenStorage.set(tokens)` | Handled automatically by `oidcClient.token.exchange()` | Tokens stored after exchange | |
| 136 | +| `TokenStorage.remove()` | `await oidcClient.token.revoke()` | Combined revoke + delete | |
| 137 | + |
| 138 | +### Token Type Change |
| 139 | + |
| 140 | +| Legacy | New | Change | |
| 141 | +| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | |
| 142 | +| `Tokens { accessToken?, idToken?, refreshToken?, tokenExpiry? }` | `OauthTokens { accessToken, idToken, refreshToken?, expiresAt?, expiryTimestamp? }` | `accessToken` and `idToken` now required. `tokenExpiry` renamed to `expiryTimestamp` | |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## Callback & WebAuthn |
| 147 | + |
| 148 | +| Legacy | New | Notes | |
| 149 | +| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------- | |
| 150 | +| `import { CallbackType } from '@forgerock/javascript-sdk'` | `import { callbackType } from '@forgerock/journey-client'` | PascalCase enum → camelCase object | |
| 151 | +| `CallbackType.RedirectCallback` | `callbackType.RedirectCallback` | Values remain the same strings | |
| 152 | +| `import { FRCallback } from '@forgerock/javascript-sdk'` | `import { BaseCallback } from '@forgerock/journey-client/types'` | Base class renamed | |
| 153 | +| `import { NameCallback, ... } from '@forgerock/javascript-sdk'` | `import { NameCallback, ... } from '@forgerock/journey-client/types'` | Same class names, different import path | |
| 154 | +| `import { FRWebAuthn, WebAuthnStepType } from '@forgerock/javascript-sdk'` | `import { WebAuthn, WebAuthnStepType } from '@forgerock/journey-client/webauthn'` | `FRWebAuthn` → `WebAuthn`, submodule import | |
| 155 | + |
| 156 | +--- |
| 157 | + |
| 158 | +## Step Types |
| 159 | + |
| 160 | +| Legacy | New | Notes | |
| 161 | +| --------------------------------- | ----------------------------------- | ----------------------------------------------------- | |
| 162 | +| `FRStep` (class instance) | `JourneyStep` (object type) | Cannot use `instanceof`; use `result.type === 'Step'` | |
| 163 | +| `FRLoginSuccess` (class instance) | `JourneyLoginSuccess` (object type) | Use `result.type === 'LoginSuccess'` | |
| 164 | +| `FRLoginFailure` (class instance) | `JourneyLoginFailure` (object type) | Use `result.type === 'LoginFailure'` | |
| 165 | + |
| 166 | +All step methods (`getCallbackOfType`, `getDescription`, `getHeader`, `getStage`, `getSessionToken`, etc.) remain identical. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## HTTP Client |
| 171 | + |
| 172 | +The legacy `HttpClient` is removed. It provided auto bearer-token injection, 401 token refresh, and policy advice handling. In the new SDK, manage tokens manually: |
| 173 | + |
| 174 | +```typescript |
| 175 | +const tokens = await oidcClient.token.get({ backgroundRenew: true }); |
| 176 | +if ('error' in tokens) { |
| 177 | + throw new Error('No valid tokens'); |
| 178 | +} |
| 179 | + |
| 180 | +const response = await fetch('https://api.example.com/resource', { |
| 181 | + method: 'GET', |
| 182 | + headers: { Authorization: `Bearer ${tokens.accessToken}` }, |
| 183 | +}); |
| 184 | +``` |
| 185 | + |
| 186 | +--- |
| 187 | + |
| 188 | +## Error Handling Pattern |
| 189 | + |
| 190 | +The new SDK returns error objects instead of throwing exceptions: |
| 191 | + |
| 192 | +| Legacy | New | |
| 193 | +| ------------------------------------------------ | ------------------------------------------------------------------------ | |
| 194 | +| `try { ... } catch (err) { console.error(err) }` | `if ('error' in result) { console.error(result.error, result.message) }` | |
| 195 | + |
| 196 | +**Legacy:** |
| 197 | + |
| 198 | +```typescript |
| 199 | +try { |
| 200 | + const user = await UserManager.getCurrentUser(); |
| 201 | + setUser(user); |
| 202 | +} catch (err) { |
| 203 | + console.error(`Error: get current user; ${err}`); |
| 204 | + setUser({}); |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +**New:** |
| 209 | + |
| 210 | +```typescript |
| 211 | +const user = await oidcClient.user.info(); |
| 212 | +if ('error' in user) { |
| 213 | + console.error('Error getting user:', user); |
| 214 | + setUser({}); |
| 215 | +} else { |
| 216 | + setUser(user); |
| 217 | +} |
| 218 | +``` |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +## Summary of Key Changes |
| 223 | + |
| 224 | +1. **Async-First Initialization**: SDK clients initialized asynchronously with factory functions (`journey()`, `oidc()`) |
| 225 | +2. **Instance-Based APIs**: Methods called on client instances, not static classes |
| 226 | +3. **Separated Packages**: Journey auth (`@forgerock/journey-client`), OIDC (`@forgerock/oidc-client`), Device (`@forgerock/device-client`), Protect (`@forgerock/protect`) |
| 227 | +4. **Explicit Error Handling**: Response objects contain `{ error }` property instead of throwing exceptions |
| 228 | +5. **Wellknown-Based Discovery**: Only `serverConfig.wellknown` is required; all paths derived automatically |
| 229 | +6. **No Built-in HTTP Client**: Protected API requests require manual token retrieval and header management |
| 230 | +7. **Config Fixed at Creation**: Per-call config overrides removed; create separate client instances for different configs |
| 231 | + |
| 232 | +--- |
| 233 | + |
| 234 | +## Further Reference |
| 235 | + |
| 236 | +For the complete API-level mapping (every class, method, parameter change, return type change, and behavioral note), see [`interface_mapping.md`](./interface_mapping.md). |
0 commit comments