Skip to content

Commit fcfb57f

Browse files
ryanbas21claude
andcommitted
docs: add README files and screenshots for all packages
Ported from ping-javascript-sdk with @forgerock/ references updated to @wolfcola/ and branding updated to WolfCola DevTools. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fe78e57 commit fcfb57f

6 files changed

Lines changed: 726 additions & 0 deletions

File tree

packages/devtools-bridge/README.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# @wolfcola/devtools-bridge
2+
3+
Opt-in SDK adapter that connects your Ping Identity / ForgeRock application to the [WolfCola DevTools extension](../devtools-extension). Add it to your app in one line — it is a no-op when the extension is not installed, so it is safe to ship in production builds.
4+
5+
## Contents
6+
7+
- [Installation](#installation)
8+
- [Bridges](#bridges)
9+
- [DaVinci — `attachDevToolsBridge`](#davinci--attachdevtoolsbridge)
10+
- [AM Journey — `attachJourneyBridge`](#am-journey--attachjourneybridge)
11+
- [OIDC / OAuth — `attachOidcBridge`](#oidc--oauth--attachoidcbridge)
12+
- [Low-level API](#low-level-api)
13+
- [How it works](#how-it-works)
14+
- [Safety](#safety)
15+
16+
---
17+
18+
## Installation
19+
20+
```bash
21+
pnpm add @wolfcola/devtools-bridge
22+
```
23+
24+
`effect` is a peer dependency. `@forgerock/davinci-client` is an optional peer dependency required only if you use `attachDevToolsBridge`.
25+
26+
---
27+
28+
## Bridges
29+
30+
### DaVinci — `attachDevToolsBridge`
31+
32+
Subscribes to a DaVinci client store and emits `sdk:node-change` on every node status transition, plus `session:cookie` / `session:storage` diffs after each transition.
33+
34+
```ts
35+
import { davinci } from '@forgerock/davinci-client';
36+
import { attachDevToolsBridge } from '@wolfcola/devtools-bridge';
37+
38+
const client = await davinci({ config });
39+
40+
// Pass config as the second argument — emitted once as sdk:config on the first transition
41+
const bridge = attachDevToolsBridge(client, config);
42+
43+
// Unsubscribe when the component unmounts
44+
bridge.detach();
45+
```
46+
47+
**What it captures per node transition:**
48+
49+
| Field | Source |
50+
| ---------------- | --------------------------------------------- |
51+
| `nodeStatus` | DaVinci node `.status` |
52+
| `previousStatus` | Previous status (tracked locally) |
53+
| `interactionId` | `server.interactionId` |
54+
| `nodeName` | `client.name` |
55+
| `collectors` | `client.collectors` (full objects) |
56+
| `error` | `error.code / message / type` |
57+
| `session` | `server.session` (DaVinci session token) |
58+
| `responseBody` | Full DaVinci server response (from RTK cache) |
59+
60+
The bridge only emits when `nodeStatus` actually changes, so rapid store updates that don't advance the node do not generate noise.
61+
62+
---
63+
64+
### AM Journey — `attachJourneyBridge`
65+
66+
Subscribes to a Journey RTK store and emits `sdk:journey-step` for each mutation that settles (`fulfilled` or `rejected`). Each event carries the full AM step response including all callbacks with their `input`/`output` arrays.
67+
68+
```ts
69+
import { journey } from '@forgerock/journey-client'; // your RTK-based journey client
70+
import { attachJourneyBridge } from '@wolfcola/devtools-bridge';
71+
72+
const client = await journey({ config });
73+
74+
attachJourneyBridge(client, config);
75+
```
76+
77+
**`JourneySubscribable` interface** — any object with this shape works:
78+
79+
```ts
80+
interface JourneySubscribable {
81+
subscribe: (listener: () => void) => () => void;
82+
getState: () => unknown; // must expose { journeyReducer: { mutations: Record<string, MutationEntry> } }
83+
}
84+
```
85+
86+
**Emitted events by step type:**
87+
88+
| `stepType` | When | Notable fields |
89+
| -------------- | --------------------------------- | ------------------------------------------ |
90+
| `Step` | AM returns `authId` | `callbacks`, `authId`, `stage`, `header` |
91+
| `LoginSuccess` | AM returns `tokenId` | `tokenId`, `successUrl` |
92+
| `LoginFailure` | AM returns an error / RTK rejects | `errorCode`, `errorMessage`, `errorReason` |
93+
94+
---
95+
96+
### OIDC / OAuth — `attachOidcBridge`
97+
98+
Subscribes to an OIDC client RTK store and emits `sdk:oidc-state` for each settled mutation. Maps RTK endpoint names to human-readable phases.
99+
100+
```ts
101+
import { oidcClient } from '@forgerock/oidc-client'; // your RTK-based OIDC client
102+
import { attachOidcBridge } from '@wolfcola/devtools-bridge';
103+
104+
const client = oidcClient({ config });
105+
106+
attachOidcBridge(client, config);
107+
```
108+
109+
**`OidcSubscribable` interface:**
110+
111+
```ts
112+
interface OidcSubscribable {
113+
subscribe: (listener: () => void) => () => void;
114+
getState: () => unknown; // must expose { oidc: { mutations: Record<string, MutationEntry> } }
115+
}
116+
```
117+
118+
**Endpoint → phase mapping:**
119+
120+
| RTK endpoint name | Emitted phase |
121+
| ----------------- | ------------- |
122+
| `authorizeFetch` | `authorize` |
123+
| `authorizeIframe` | `authorize` |
124+
| `exchange` | `exchange` |
125+
| `revoke` | `revoke` |
126+
| `userInfo` | `userinfo` |
127+
| `endSession` | `logout` |
128+
129+
Pass `config.clientId` to surface it in the extension's node detail card:
130+
131+
```ts
132+
attachOidcBridge(client, { clientId: 'my-spa-client', ...rest });
133+
```
134+
135+
---
136+
137+
## Low-level API
138+
139+
If you need to emit events from outside a supported client, use the primitives directly.
140+
141+
```ts
142+
import { emitAuthEvent, emitConfigEvent, DEVTOOLS_EVENT_NAME } from '@wolfcola/devtools-bridge';
143+
144+
emitAuthEvent({
145+
id: crypto.randomUUID(),
146+
timestamp: performance.now(),
147+
type: 'sdk:node-change',
148+
source: 'sdk',
149+
flowId: null,
150+
causedBy: null,
151+
data: { _tag: 'sdk', nodeStatus: 'next' },
152+
flags: { isCors: false, isError: false, isAuthRelated: true },
153+
});
154+
155+
emitConfigEvent({ clientId: 'my-app', environment: 'dev' });
156+
```
157+
158+
Both functions dispatch a `CustomEvent` named `DEVTOOLS_EVENT_NAME` (`'pingDevtools'`) on `window`. The content script picks this up and forwards it to the extension service worker.
159+
160+
---
161+
162+
## How it works
163+
164+
```
165+
Your app
166+
├── attachDevToolsBridge(davinciClient) ─┐
167+
├── attachJourneyBridge(journeyClient) ─┤─ emitAuthEvent()
168+
└── attachOidcBridge(oidcClient) ─┘
169+
170+
│ window.dispatchEvent(new CustomEvent('pingDevtools', { detail: event }))
171+
172+
content-script.js
173+
174+
│ chrome.runtime.sendMessage({ type: 'SDK_EVENT', payload: event })
175+
176+
service-worker.ts ──(validates via AuthEventSchema)──▶ EventStore
177+
178+
│ chrome.runtime.sendMessage({ type: 'EVENTS_UPDATED' })
179+
180+
panel (Elm) ── Timeline view + Flow view
181+
```
182+
183+
Each bridge function:
184+
185+
1. Subscribes to the client store
186+
2. Validates the current state with an Effect Schema decoder (returns `Option.none` on mismatch — never throws)
187+
3. Deduplicates by tracking already-emitted request IDs in a `Set`
188+
4. Trims that `Set` to only IDs still present in the store, bounding memory use
189+
5. Dispatches the event only when `window.__PING_DEVTOOLS_EXTENSION__` is present
190+
191+
---
192+
193+
## Safety
194+
195+
- **No-op without the extension** — all bridges check for `window.__PING_DEVTOOLS_EXTENSION__` before dispatching. If the marker is absent, nothing is emitted.
196+
- **No-op in SSR / Node** — all bridges return `{ detach: () => undefined }` immediately when `typeof window === 'undefined'`.
197+
- **Tree-shakeable**`sideEffects: false` in `package.json`; unused bridges are eliminated by your bundler.
198+
- **No sensitive data leakage** — the bridge never reads passwords or form values; it only observes the client's Redux/RTK state.

0 commit comments

Comments
 (0)