farvex 0.1.0 → 0.2.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/CHANGELOG.md +22 -0
- package/README.md +136 -50
- package/dist/core/index.cjs +2912 -3
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +1416 -2
- package/dist/core/index.d.ts +1416 -2
- package/dist/core/index.js +2899 -3
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +2900 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2893 -3
- package/dist/index.js.map +1 -1
- package/dist/livekit/index.cjs +105 -0
- package/dist/livekit/index.cjs.map +1 -0
- package/dist/livekit/index.d.cts +2 -0
- package/dist/livekit/index.d.ts +2 -0
- package/dist/livekit/index.js +4 -0
- package/dist/livekit/index.js.map +1 -0
- package/dist/mock/index.cjs +3267 -0
- package/dist/mock/index.cjs.map +1 -0
- package/dist/mock/index.d.cts +31 -0
- package/dist/mock/index.d.ts +31 -0
- package/dist/mock/index.js +3263 -0
- package/dist/mock/index.js.map +1 -0
- package/dist/react/index.cjs +3315 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +87 -2
- package/dist/react/index.d.ts +87 -2
- package/dist/react/index.js +3297 -3
- package/dist/react/index.js.map +1 -1
- package/package.json +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# farvex
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **BREAKING**: Replaced Offer with Invite. `OfferManager` → `InviteManager`, `Offer` → `Invite`, `useIncomingOffer` → `useIncomingInvite`, pulse topics `session.offer.*` → `session.invite.*`, endpoints `/sessions/offers/{offerId}/*` → `/sessions/{sessionId}/invites/{inviteId}/*`. No deprecation shims — all offer-era symbols are removed.
|
|
8
|
+
- **BREAKING**: `Session.telephonyLegs` is now derived from `participant.operations[]` where `kind === "sip_leg"`. The backend no longer returns a top-level `telephonyLegs[]`.
|
|
9
|
+
- **BREAKING**: Participant lifecycle states changed. Removed `ringing`, `joining`. Added `accepted`, `declined`, `withdrawn`. New `SessionEventMap` keys: `participant.accepted`, `participant.declined`, `participant.withdrawn`.
|
|
10
|
+
- **BREAKING**: `isSelf` is now keyed by `participantId` instead of `participantIdentity`. `SessionManagerOptions.selfIdentity` renamed to `selfParticipantId`. The join grant now carries `participantId`; `Session.join()` uses it.
|
|
11
|
+
- **BREAKING**: `useCallControls` controls now expose `enabled: boolean` on each slot and gate actions by the current participant's `capabilities[]`. `leave`/`end` return `{enabled, run}` instead of a bare function. Disabled when capability is missing; never hidden.
|
|
12
|
+
- Added `Dispatch` surface (ring-group) with `session.dispatches`, `useSessionDispatches`, and events: `dispatch.started`, `dispatch.resolved`, `dispatch.cancelled`, `dispatch.expired`.
|
|
13
|
+
- Added `SessionRouteContext` accessible as `session.route`.
|
|
14
|
+
- Added `useSessionCapabilities(sessionId?)` returning a capability `Set` for self.
|
|
15
|
+
- Added `session.capabilitiesOf(participantId?)`.
|
|
16
|
+
- Added `InviteManager.dropBySession` triggered on `session.remove` pulse messages so invites tied to ended sessions are reaped without waiting for per-invite removes.
|
|
17
|
+
- Tests now live beside source (`foo.test.ts` next to `foo.ts`).
|
|
18
|
+
- Regenerated API types against the rewritten session module (JSON-aggregate backend).
|
|
19
|
+
- Added `./livekit` and `./mock` subpath exports.
|
|
20
|
+
- Added `Session.mediaRoom` getter exposing the underlying `livekit-client` `Room`, so the React provider can share it with the LiveKit context.
|
|
21
|
+
- Fixed: `CallpadProvider` now reuses the SDK's internal `Room` via `<RoomContext.Provider>` instead of instantiating a second `Room` through `<LiveKitRoom>`. Eliminates duplicate-identity races; mute state reflects the `Room` that actually publishes audio.
|
|
22
|
+
- Fixed: `SessionEffectsExecutor` DI binding was missing from `sessionModule`; webhook jobs failed silently with `No bindings found for service: "SessionEffectsExecutor"`.
|
|
23
|
+
- Fixed: inbound SIP caller participant is now created with `initialState: "joined"` (was `"accepted"`). Normal hangups now emit `session.participant.left` (reason `left`) instead of `session.participant.failed` (reason `unreachable`).
|
|
24
|
+
|
|
3
25
|
## 0.1.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Web SDK for Callpad sessions — a thin, React-friendly wrapper over LiveKit and the Callpad Pulse realtime layer for managing WebRTC and SIP sessions.
|
|
4
4
|
|
|
5
|
-
> Scaffold state. No SDK surface is exposed yet beyond placeholder exports that prove the build pipeline. The first published version will be `0.1.0`; the real SDK surface lands in the next iteration.
|
|
6
|
-
|
|
7
5
|
## Install
|
|
8
6
|
|
|
9
7
|
```sh
|
|
@@ -22,30 +20,123 @@ pnpm add react react-dom
|
|
|
22
20
|
|
|
23
21
|
## Subpath exports
|
|
24
22
|
|
|
25
|
-
| Entry
|
|
26
|
-
|
|
|
27
|
-
| `farvex`
|
|
28
|
-
| `farvex/core`
|
|
29
|
-
| `farvex/react`
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
| Entry | Use when |
|
|
24
|
+
| ---------------- | -------------------------------------------------------------- |
|
|
25
|
+
| `farvex` | Curated top-level exports. |
|
|
26
|
+
| `farvex/core` | Framework-agnostic client (browser-safe at module load). |
|
|
27
|
+
| `farvex/react` | React hooks and providers. Carries `"use client"` for Next.js. |
|
|
28
|
+
| `farvex/livekit` | Re-exports livekit-client + @livekit/components-react types. |
|
|
29
|
+
| `farvex/mock` | In-memory mock client for tests and storybook. |
|
|
30
|
+
|
|
31
|
+
## Quickstart (React)
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import {
|
|
35
|
+
CallpadProvider,
|
|
36
|
+
useIncomingInvite,
|
|
37
|
+
useCallControls,
|
|
38
|
+
} from "farvex/react";
|
|
39
|
+
|
|
40
|
+
export default function App() {
|
|
41
|
+
return (
|
|
42
|
+
<CallpadProvider
|
|
43
|
+
config={{
|
|
44
|
+
apiBaseUrl: "https://api.callpad.example.com",
|
|
45
|
+
vendor: "acme",
|
|
46
|
+
auth: { getAccessToken: async () => myToken() },
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<CallScreen />
|
|
50
|
+
</CallpadProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function CallScreen() {
|
|
55
|
+
const invite = useIncomingInvite();
|
|
56
|
+
const controls = useCallControls();
|
|
57
|
+
|
|
58
|
+
if (invite) {
|
|
59
|
+
return (
|
|
60
|
+
<div>
|
|
61
|
+
<p>Incoming invite from {invite.inviterParticipantId ?? "unknown"}</p>
|
|
62
|
+
<button onClick={() => invite.accept()}>Accept</button>
|
|
63
|
+
<button onClick={() => invite.reject()}>Decline</button>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div>
|
|
70
|
+
<button disabled={!controls.hold.enabled} onClick={controls.hold.toggle}>
|
|
71
|
+
{controls.hold.active ? "Resume" : "Hold"}
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
disabled={!controls.recording.enabled}
|
|
75
|
+
onClick={controls.recording.toggle}
|
|
76
|
+
>
|
|
77
|
+
{controls.recording.active ? "Stop recording" : "Record"}
|
|
78
|
+
</button>
|
|
79
|
+
<button disabled={!controls.end.enabled} onClick={controls.end.run}>
|
|
80
|
+
End
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
35
85
|
```
|
|
36
86
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
87
|
+
Controls are capability-gated: the backend sends a per-participant `capabilities[]` list, and hooks return `enabled: false` when the required capability is missing. Render them disabled rather than hidden.
|
|
88
|
+
|
|
89
|
+
## Core concepts
|
|
90
|
+
|
|
91
|
+
- **Session** — one call. Has `participants`, `dispatches`, `recordings`, `route`. State is derived: `ringing | active | on_hold | ended`. Telephony legs (SIP) live inside each participant's `operations[]`.
|
|
92
|
+
- **Invite** — a pending app invite sent to a specific recipient (agent or user). The recipient accepts via `POST /api/v1/sessions/{sessionId}/invites/{inviteId}/accept`.
|
|
93
|
+
- **Dispatch** — a ring-group: one or more agents rung in parallel or sequentially. When one answers, the dispatch `resolves` and the siblings transition to `withdrawn`.
|
|
94
|
+
- **Capability** — per-participant permission: `hold_session`, `end_session`, `start_recording`, etc. Use `session.capabilitiesOf()` to gate UI.
|
|
95
|
+
|
|
96
|
+
## React hooks
|
|
97
|
+
|
|
98
|
+
| Hook | Purpose |
|
|
99
|
+
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
100
|
+
| `useCallpadClient()` | Access underlying client + connection status. |
|
|
101
|
+
| `useSessions()` / `useSession(id?)` | All sessions / active session or by id. |
|
|
102
|
+
| `useIncomingInvite()` / `useIncomingInvites()` | First pending invite / all pending invites for the current user. |
|
|
103
|
+
| `useSessionParticipants(id?)` / `useSessionParticipant(pid, id?)` / `useSelfParticipant(id?)` | Participants in a session. |
|
|
104
|
+
| `useSessionCapabilities(id?)` | Capability `Set` for self in the active session. |
|
|
105
|
+
| `useSessionDispatches(id?)` | Active ring-group dispatches in a session. |
|
|
106
|
+
| `useCallControls(id?)` | Capability-gated mic / camera / screen / hold / recording / leave / end. |
|
|
107
|
+
| `useCallDuration(id?)` | `{formatted, elapsedSeconds}` since session activated. |
|
|
108
|
+
| `useIsHeld(id?)` | Whether the session is on hold. |
|
|
109
|
+
| `useActiveRecording(id?)` | Current in-progress recording (if any). |
|
|
110
|
+
| `useSessionEvent(event, handler, id?)` | Subscribe to a session event with auto cleanup. |
|
|
111
|
+
| `useClientEvent(event, handler)` | Subscribe to a client event with auto cleanup. |
|
|
112
|
+
| `usePresence(refs)` / `useMyPresence()` | Presence query + my presence control. |
|
|
113
|
+
|
|
114
|
+
## Realtime events
|
|
115
|
+
|
|
116
|
+
Pulse pushes these topics (SDK forwards them on `PulseTransport`):
|
|
117
|
+
|
|
118
|
+
| Topic | Payload |
|
|
119
|
+
| ----------------------- | ------------------------------------------------------------------------ |
|
|
120
|
+
| `session.upsert` | Full `SessionView`. |
|
|
121
|
+
| `session.remove` | `{sessionId, version}`. Also drops any pending invites for that session. |
|
|
122
|
+
| `session.invite.upsert` | `SessionInviteView` — add/update a pending invite for the current user. |
|
|
123
|
+
| `session.invite.remove` | `{inviteId, sessionId, participantId, state}`. |
|
|
124
|
+
|
|
125
|
+
## HTTP endpoints used
|
|
126
|
+
|
|
127
|
+
All under `/api/v1`:
|
|
128
|
+
|
|
129
|
+
- `GET /sessions` — prime the session list.
|
|
130
|
+
- `POST /sessions` — create a session.
|
|
131
|
+
- `GET /sessions/{sessionId}` — fetch by id.
|
|
132
|
+
- `PATCH /sessions/{sessionId}` — update name.
|
|
133
|
+
- `POST /sessions/{sessionId}/join` — get a LiveKit token grant.
|
|
134
|
+
- `POST /sessions/{sessionId}/hold` / `.../unhold` / `.../cancel` / `.../leave` / `.../end`
|
|
135
|
+
- `POST /sessions/{sessionId}/participants` — invite a target (agent/user/contact/phone).
|
|
136
|
+
- `DELETE /sessions/{sessionId}/participants/{participantId}` — remove a participant.
|
|
137
|
+
- `POST /sessions/{sessionId}/recordings` / `.../recordings/{recordingId}/stop` — recording control.
|
|
138
|
+
- `GET /sessions/invites` — list pending invites for the current user.
|
|
139
|
+
- `POST /sessions/{sessionId}/invites/{inviteId}/accept` / `.../reject` — act on an invite.
|
|
49
140
|
|
|
50
141
|
## Local development
|
|
51
142
|
|
|
@@ -54,11 +145,18 @@ From `websdk/`:
|
|
|
54
145
|
```sh
|
|
55
146
|
bun install
|
|
56
147
|
bun run dev # tsup watch mode; rebuilds dist/ on change
|
|
57
|
-
bun run test # vitest
|
|
148
|
+
bun run test # vitest; includes unit tests next to source files
|
|
58
149
|
bun run check # lint + format + typecheck
|
|
59
150
|
bun run build # one-off production build
|
|
60
151
|
```
|
|
61
152
|
|
|
153
|
+
API types are generated from the backend's OpenAPI spec:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
# start the backend first: `bun run docker:up && docker compose up callpad.http`
|
|
157
|
+
bun run generate:api # writes src/api/generated/
|
|
158
|
+
```
|
|
159
|
+
|
|
62
160
|
### Linking into a consumer app (goagentfe, customer app, etc.)
|
|
63
161
|
|
|
64
162
|
We recommend [`yalc`](https://github.com/wclr/yalc) over `pnpm link` / `bun link` because it survives Vite and Next.js module caches.
|
|
@@ -85,7 +183,7 @@ Each change in `websdk/`:
|
|
|
85
183
|
npx yalc push # push built dist/ to subscribers
|
|
86
184
|
```
|
|
87
185
|
|
|
88
|
-
Before committing in the consumer app, undo the local link
|
|
186
|
+
Before committing in the consumer app, undo the local link:
|
|
89
187
|
|
|
90
188
|
```sh
|
|
91
189
|
npx yalc remove --all
|
|
@@ -94,36 +192,22 @@ pnpm install
|
|
|
94
192
|
|
|
95
193
|
## Release flow
|
|
96
194
|
|
|
97
|
-
See `sdk-scaffold.md` at the repo root for the full plan. Short version (manual, no CI yet):
|
|
98
|
-
|
|
99
195
|
```sh
|
|
100
|
-
# while working on a change
|
|
101
|
-
bun run changeset
|
|
102
|
-
|
|
103
|
-
# when ready to publish (from websdk/), on clean main:
|
|
196
|
+
bun run changeset # while working on a change
|
|
104
197
|
bun run version # applies pending changesets, writes CHANGELOG.md
|
|
105
198
|
git commit -am "chore: release farvex@x.y.z"
|
|
106
199
|
bun run release # builds, runs changeset publish
|
|
107
200
|
git push --follow-tags
|
|
108
201
|
```
|
|
109
202
|
|
|
110
|
-
Before the first real publish:
|
|
111
|
-
|
|
112
|
-
```sh
|
|
113
|
-
npm login # as the publisher account
|
|
114
|
-
npm view farvex # must return 404
|
|
115
|
-
bun run build
|
|
116
|
-
bunx publint
|
|
117
|
-
bun pm pack # inspect the tarball contents
|
|
118
|
-
```
|
|
119
|
-
|
|
120
203
|
Pre-1.0: every feature ships as a `minor` bump; breaking changes allowed without a major.
|
|
121
204
|
|
|
122
205
|
## Package conventions
|
|
123
206
|
|
|
124
207
|
- Side-effect free — `"sideEffects": false` in `package.json`. Tree-shakes cleanly.
|
|
125
208
|
- No top-level access to `window`, `document`, `navigator`, `RTCPeerConnection`, or `localStorage`. Any such access happens inside methods or effects.
|
|
126
|
-
- `
|
|
209
|
+
- No `any` or `unknown` in exported types.
|
|
210
|
+
- Tests live next to source (`foo.test.ts` beside `foo.ts`).
|
|
127
211
|
- `engines.node >= 18`.
|
|
128
212
|
|
|
129
213
|
## Layout
|
|
@@ -131,14 +215,16 @@ Pre-1.0: every feature ships as a `minor` bump; breaking changes allowed without
|
|
|
131
215
|
```
|
|
132
216
|
websdk/
|
|
133
217
|
src/
|
|
134
|
-
index.ts public entry
|
|
135
|
-
core/
|
|
136
|
-
|
|
137
|
-
|
|
218
|
+
index.ts public entry
|
|
219
|
+
core/ framework-agnostic client
|
|
220
|
+
session/ Session class + commands
|
|
221
|
+
managers/ SessionManager, InviteManager, PresenceManager
|
|
222
|
+
transport/ PulseTransport, MediaRoomAdapter
|
|
223
|
+
react/ React hooks + providers
|
|
224
|
+
mock/ in-memory mock client + scenarios
|
|
225
|
+
test/ setup + cross-cutting smoke tests
|
|
138
226
|
dist/ build output (gitignored)
|
|
139
227
|
tsup.config.ts build config (ESM + CJS + dts, use-client preserved)
|
|
140
228
|
tsconfig.json strict TS, bundler resolution
|
|
141
|
-
vitest.config.ts jsdom test env
|
|
142
|
-
.oxlintrc.json lint rules (matches apps/main)
|
|
143
|
-
.oxfmtrc.json format rules (matches apps/main)
|
|
229
|
+
vitest.config.ts jsdom test env (includes src/**/*.test.ts)
|
|
144
230
|
```
|