Skip to content

Commit 7b21957

Browse files
committed
feat: add api-report tool, interface mapping validator, and migration guide updates
Adds forgotten-export detection via api-extractor, interface mapping validation between legacy and new client packages, migration to lefthook, and initial API report generation for all client packages. Updates interface_mapping.md to match current SDK exports and syncs api-report files that drifted during the rebase.
1 parent 03fe141 commit 7b21957

104 files changed

Lines changed: 14161 additions & 378 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"@forgerock/device-client-app",
1919
"@forgerock/davinci-app",
2020
"@forgerock/davinci-suites",
21+
"@forgerock/api-report",
22+
"@forgerock/interface-mapping-validator",
2123
"@forgerock/mock-api-v2",
2224
"@forgerock/oidc-app",
2325
"@forgerock/oidc-suites",

.changeset/deep-spies-kick.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@forgerock/davinci-client': patch
3+
'@forgerock/journey-client': patch
4+
'@forgerock/device-client': patch
5+
'@forgerock/oidc-client': patch
6+
---
7+
8+
Update interfaces and types that are missing from exports

.github/workflows/ci.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,70 @@ jobs:
5959
with:
6060
message: Deployed ${{ github.sha }} to https://ForgeRock.github.io/ping-javascript-sdk/pr-${{ github.event.number }}/${{github.sha}} branch gh-pages in ForgeRock/ping-javascript-sdk
6161

62+
- name: Validate interface mapping
63+
id: interface-mapping
64+
continue-on-error: true
65+
run: |
66+
set +e
67+
OUTPUT=$(pnpm exec tsx tools/interface-mapping-validator/src/main.ts 2>&1)
68+
EXIT_CODE=$?
69+
set -e
70+
echo "$OUTPUT"
71+
echo 'report<<MAPPING_EOF' >> "$GITHUB_OUTPUT"
72+
echo "$OUTPUT" >> "$GITHUB_OUTPUT"
73+
echo 'MAPPING_EOF' >> "$GITHUB_OUTPUT"
74+
exit $EXIT_CODE
75+
76+
- name: Find interface mapping comment
77+
id: find-mapping-comment
78+
if: always()
79+
uses: peter-evans/find-comment@v4
80+
with:
81+
issue-number: ${{ github.event.pull_request.number }}
82+
comment-author: 'github-actions[bot]'
83+
body-includes: <!-- interface-mapping-check -->
84+
85+
- name: Update interface mapping comment
86+
if: always() && steps.interface-mapping.outcome == 'failure'
87+
uses: peter-evans/create-or-update-comment@v5
88+
with:
89+
comment-id: ${{ steps.find-mapping-comment.outputs.comment-id }}
90+
issue-number: ${{ github.event.pull_request.number }}
91+
edit-mode: replace
92+
body: |
93+
<!-- interface-mapping-check -->
94+
## Interface Mapping Out of Date
95+
96+
The `interface_mapping.md` document is out of sync with the SDK exports.
97+
98+
<details>
99+
<summary>Drift report</summary>
100+
101+
```
102+
${{ steps.interface-mapping.outputs.report }}
103+
```
104+
105+
</details>
106+
107+
**To fix**, run:
108+
```bash
109+
pnpm mapping:generate
110+
```
111+
Then commit the updated `interface_mapping.md`.
112+
113+
- name: Update interface mapping comment (passing)
114+
if: always() && steps.interface-mapping.outcome == 'success' && steps.find-mapping-comment.outputs.comment-id != ''
115+
uses: peter-evans/create-or-update-comment@v5
116+
with:
117+
comment-id: ${{ steps.find-mapping-comment.outputs.comment-id }}
118+
issue-number: ${{ github.event.pull_request.number }}
119+
edit-mode: replace
120+
body: |
121+
<!-- interface-mapping-check -->
122+
## Interface Mapping Up to Date
123+
124+
The `interface_mapping.md` document is in sync with the SDK exports.
125+
62126
- name: Download baseline bundle sizes
63127
uses: dawidd6/action-download-artifact@v3
64128
with:

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ out-tsc/
2828
.swc
2929
.vite
3030

31+
# Lefthook
32+
.lefthook-local.yml
33+
.lefthook/
34+
3135
# IDEs
3236
.vscode
3337

@@ -77,6 +81,8 @@ packages/davinci-client/src/lib/mock-data/*.d.ts
7781
packages/davinci-client/src/lib/mock-data/*.d.ts.map
7882
**/**/tsconfig.tsbuildinfo
7983
**/**/tsconfig.spec.vitest-temp.json
84+
# api-extractor temp directories
85+
packages/*/temp/
8086

8187
test-output
8288
.cursor/rules/nx-rules.mdc

.husky/commit-msg

Lines changed: 0 additions & 1 deletion
This file was deleted.

.husky/install.mjs

Lines changed: 0 additions & 6 deletions
This file was deleted.

.husky/pre-commit

Lines changed: 0 additions & 1 deletion
This file was deleted.

.husky/pre-push

Lines changed: 0 additions & 1 deletion
This file was deleted.

.husky/prepare-commit-msg

Lines changed: 0 additions & 3 deletions
This file was deleted.

MIGRATION.md

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

Comments
 (0)