Skip to content

Commit 6aac711

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.
1 parent 03fe141 commit 6aac711

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: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,68 @@ 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+
OUTPUT=$(pnpm mapping:validate 2>&1)
67+
EXIT_CODE=$?
68+
echo "$OUTPUT"
69+
echo 'report<<MAPPING_EOF' >> "$GITHUB_OUTPUT"
70+
echo "$OUTPUT" >> "$GITHUB_OUTPUT"
71+
echo 'MAPPING_EOF' >> "$GITHUB_OUTPUT"
72+
exit $EXIT_CODE
73+
74+
- name: Find interface mapping comment
75+
id: find-mapping-comment
76+
if: always()
77+
uses: peter-evans/find-comment@v4
78+
with:
79+
issue-number: ${{ github.event.pull_request.number }}
80+
comment-author: 'github-actions[bot]'
81+
body-includes: <!-- interface-mapping-check -->
82+
83+
- name: Update interface mapping comment
84+
if: always() && steps.interface-mapping.outcome == 'failure'
85+
uses: peter-evans/create-or-update-comment@v5
86+
with:
87+
comment-id: ${{ steps.find-mapping-comment.outputs.comment-id }}
88+
issue-number: ${{ github.event.pull_request.number }}
89+
edit-mode: replace
90+
body: |
91+
<!-- interface-mapping-check -->
92+
## Interface Mapping Out of Date
93+
94+
The `interface_mapping.md` document is out of sync with the SDK exports.
95+
96+
<details>
97+
<summary>Drift report</summary>
98+
99+
```
100+
${{ steps.interface-mapping.outputs.report }}
101+
```
102+
103+
</details>
104+
105+
**To fix**, run:
106+
```bash
107+
pnpm mapping:generate
108+
```
109+
Then commit the updated `interface_mapping.md`.
110+
111+
- name: Update interface mapping comment (passing)
112+
if: always() && steps.interface-mapping.outcome == 'success' && steps.find-mapping-comment.outputs.comment-id != ''
113+
uses: peter-evans/create-or-update-comment@v5
114+
with:
115+
comment-id: ${{ steps.find-mapping-comment.outputs.comment-id }}
116+
issue-number: ${{ github.event.pull_request.number }}
117+
edit-mode: replace
118+
body: |
119+
<!-- interface-mapping-check -->
120+
## Interface Mapping Up to Date
121+
122+
The `interface_mapping.md` document is in sync with the SDK exports.
123+
62124
- name: Download baseline bundle sizes
63125
uses: dawidd6/action-download-artifact@v3
64126
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: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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+
// Shared config — both clients can use the same base config
34+
const config = {
35+
serverConfig: {
36+
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
37+
},
38+
clientId: 'my-app',
39+
redirectUri: `${window.location.origin}/callback`,
40+
scope: 'openid profile',
41+
};
42+
43+
// Journey client — for authentication tree flows
44+
// (accepts but ignores clientId, redirectUri, scope with a console warning)
45+
const journeyClient = await journey({ config });
46+
47+
// OIDC client — for token management, user info, logout
48+
// (requires clientId, redirectUri, scope)
49+
const oidcClient = await oidc({ config });
50+
```
51+
52+
---
53+
54+
## Authentication & Journey Flow
55+
56+
| Legacy Method | New Method | Notes |
57+
| -------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------ |
58+
| `FRAuth.start({ tree: 'Login' })` | `journeyClient.start({ journey: 'Login' })` | Tree name passed per-call via `journey` param (not in config) |
59+
| `FRAuth.next(step, { tree: 'Login' })` | `journeyClient.next(step)` | Tree is set at `start()`, not repeated on `next()` |
60+
| `FRAuth.redirect(step)` | `await journeyClient.redirect(step)` | Now async. Step stored in `sessionStorage` (was `localStorage`) |
61+
| `FRAuth.resume(resumeUrl)` | `await journeyClient.resume(resumeUrl)` | Previous step retrieved from `sessionStorage` (was `localStorage`) |
62+
| No equivalent | `await journeyClient.terminate()` | **New.** Ends the AM session via `/sessions` endpoint |
63+
64+
### Before/After: Login Flow
65+
66+
**Legacy:**
67+
68+
```typescript
69+
import { FRAuth, StepType } from '@forgerock/javascript-sdk';
70+
71+
Config.set({
72+
serverConfig: { baseUrl: 'https://am.example.com/am' },
73+
realmPath: 'alpha',
74+
tree: 'Login',
75+
});
76+
77+
let step = await FRAuth.start();
78+
while (step.type === StepType.Step) {
79+
// ... handle callbacks ...
80+
step = await FRAuth.next(step);
81+
}
82+
if (step.type === StepType.LoginSuccess) {
83+
console.log('Session token:', step.getSessionToken());
84+
}
85+
```
86+
87+
**New:**
88+
89+
```typescript
90+
import { journey } from '@forgerock/journey-client';
91+
92+
const journeyClient = await journey({
93+
config: {
94+
serverConfig: {
95+
wellknown: 'https://am.example.com/am/oauth2/alpha/.well-known/openid-configuration',
96+
},
97+
},
98+
});
99+
100+
let result = await journeyClient.start({ journey: 'Login' });
101+
while (result.type === 'Step') {
102+
// ... handle callbacks ...
103+
result = await journeyClient.next(result);
104+
}
105+
if ('error' in result) {
106+
console.error('Journey error:', result);
107+
} else if (result.type === 'LoginSuccess') {
108+
console.log('Session token:', result.getSessionToken());
109+
}
110+
```
111+
112+
---
113+
114+
## User Management
115+
116+
| Legacy | New | Notes |
117+
| -------------------------------------- | -------------------------------- | -------------------------------------------------------- |
118+
| `UserManager.getCurrentUser()` | `await oidcClient.user.info()` | Returns typed `UserInfoResponse` or `GenericError` |
119+
| `FRUser.logout({ logoutRedirectUri })` | `await oidcClient.user.logout()` | Revokes tokens + ends session. Returns structured result |
120+
121+
---
122+
123+
## Token Management & OAuth Flow
124+
125+
| Legacy | New | Notes |
126+
| ------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
127+
| `TokenManager.getTokens({ forceRenew: true })` | `await oidcClient.token.get({ forceRenew: true, backgroundRenew: true })` | Single call with auto-renewal. Returns tokens or error |
128+
| `TokenManager.getTokens()` then manual code+state | `await oidcClient.authorize.background()` then `await oidcClient.token.exchange(code, state)` | Two-step when you need explicit control |
129+
| `TokenManager.deleteTokens()` | `await oidcClient.token.revoke()` | Revokes remotely AND deletes locally |
130+
| `TokenStorage.get()` | `await oidcClient.token.get()` | Auto-retrieves from storage; check `'error' in tokens` |
131+
| `TokenStorage.set(tokens)` | Handled automatically by `oidcClient.token.exchange()` | Tokens stored after exchange |
132+
| `TokenStorage.remove()` | `await oidcClient.token.revoke()` | Combined revoke + delete |
133+
134+
### Token Type Change
135+
136+
| Legacy | New | Change |
137+
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
138+
| `Tokens { accessToken?, idToken?, refreshToken?, tokenExpiry? }` | `OauthTokens { accessToken, idToken, refreshToken?, expiresAt?, expiryTimestamp? }` | `accessToken` and `idToken` now required. `tokenExpiry` renamed to `expiryTimestamp` |
139+
140+
---
141+
142+
## Callback & WebAuthn
143+
144+
| Legacy | New | Notes |
145+
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------- |
146+
| `import { CallbackType } from '@forgerock/javascript-sdk'` | `import { callbackType } from '@forgerock/journey-client'` | PascalCase enum → camelCase object |
147+
| `CallbackType.RedirectCallback` | `callbackType.RedirectCallback` | Values remain the same strings |
148+
| `import { FRCallback } from '@forgerock/javascript-sdk'` | `import { BaseCallback } from '@forgerock/journey-client/types'` | Base class renamed |
149+
| `import { NameCallback, ... } from '@forgerock/javascript-sdk'` | `import { NameCallback, ... } from '@forgerock/journey-client/types'` | Same class names, different import path |
150+
| `import { FRWebAuthn, WebAuthnStepType } from '@forgerock/javascript-sdk'` | `import { WebAuthn, WebAuthnStepType } from '@forgerock/journey-client/webauthn'` | `FRWebAuthn``WebAuthn`, submodule import |
151+
152+
---
153+
154+
## Step Types
155+
156+
| Legacy | New | Notes |
157+
| --------------------------------- | ----------------------------------- | ----------------------------------------------------- |
158+
| `FRStep` (class instance) | `JourneyStep` (object type) | Cannot use `instanceof`; use `result.type === 'Step'` |
159+
| `FRLoginSuccess` (class instance) | `JourneyLoginSuccess` (object type) | Use `result.type === 'LoginSuccess'` |
160+
| `FRLoginFailure` (class instance) | `JourneyLoginFailure` (object type) | Use `result.type === 'LoginFailure'` |
161+
162+
All step methods (`getCallbackOfType`, `getDescription`, `getHeader`, `getStage`, `getSessionToken`, etc.) remain identical.
163+
164+
---
165+
166+
## HTTP Client
167+
168+
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:
169+
170+
```typescript
171+
const tokens = await oidcClient.token.get({ backgroundRenew: true });
172+
if ('error' in tokens) {
173+
throw new Error('No valid tokens');
174+
}
175+
176+
const response = await fetch('https://api.example.com/resource', {
177+
method: 'GET',
178+
headers: { Authorization: `Bearer ${tokens.accessToken}` },
179+
});
180+
```
181+
182+
---
183+
184+
## Error Handling Pattern
185+
186+
The new SDK primarily returns error objects instead of throwing exceptions. However, some `journey-client` methods may still throw in certain edge cases (known tech debt). Defensive code should handle both patterns:
187+
188+
| Legacy | New |
189+
| ------------------------------------------------ | ------------------------------------------------------------------------ |
190+
| `try { ... } catch (err) { console.error(err) }` | `if ('error' in result) { console.error(result.error, result.message) }` |
191+
192+
**Legacy:**
193+
194+
```typescript
195+
try {
196+
const user = await UserManager.getCurrentUser();
197+
setUser(user);
198+
} catch (err) {
199+
console.error(`Error: get current user; ${err}`);
200+
setUser({});
201+
}
202+
```
203+
204+
**New:**
205+
206+
```typescript
207+
const user = await oidcClient.user.info();
208+
if ('error' in user) {
209+
console.error('Error getting user:', user);
210+
setUser({});
211+
} else {
212+
setUser(user);
213+
}
214+
```
215+
216+
---
217+
218+
## Summary of Key Changes
219+
220+
1. **Async-First Initialization**: SDK clients initialized asynchronously with factory functions (`journey()`, `oidc()`)
221+
2. **Instance-Based APIs**: Methods called on client instances, not static classes
222+
3. **Separated Packages**: Journey auth (`@forgerock/journey-client`), OIDC (`@forgerock/oidc-client`), Device (`@forgerock/device-client`), Protect (`@forgerock/protect`)
223+
4. **Explicit Error Handling**: Response objects contain `{ error }` property instead of throwing exceptions
224+
5. **Wellknown-Based Discovery**: Only `serverConfig.wellknown` is required; all paths derived automatically
225+
6. **No Built-in HTTP Client**: Protected API requests require manual token retrieval and header management
226+
7. **Config Fixed at Creation**: Per-call config overrides removed; create separate client instances for different configs
227+
228+
---
229+
230+
## Further Reference
231+
232+
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)