farvex 0.2.0 → 1.0.1
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 +18 -1
- package/README.md +292 -175
- package/dist/index.cjs +1045 -2471
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +1043 -2464
- package/dist/index.js.map +1 -1
- package/dist/livekit/index.cjs +4 -24
- package/dist/livekit/index.d.cts +2 -2
- package/dist/livekit/index.d.ts +2 -2
- package/dist/livekit/index.js +1 -1
- package/dist/react/index.cjs +89 -3287
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +18 -84
- package/dist/react/index.d.ts +18 -84
- package/dist/react/index.js +88 -3276
- package/dist/react/index.js.map +1 -1
- package/dist/types-Dgwrb3rf.d.cts +206 -0
- package/dist/types-Dgwrb3rf.d.ts +206 -0
- package/package.json +52 -71
- package/dist/core/index.cjs +0 -2917
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -1417
- package/dist/core/index.d.ts +0 -1417
- package/dist/core/index.js +0 -2902
- package/dist/core/index.js.map +0 -1
- package/dist/mock/index.cjs +0 -3267
- package/dist/mock/index.cjs.map +0 -1
- package/dist/mock/index.d.cts +0 -31
- package/dist/mock/index.d.ts +0 -31
- package/dist/mock/index.js +0 -3263
- package/dist/mock/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
# farvex
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix host client disposal, expire stale incoming invites, and export host start input types for frontend integrations.
|
|
8
|
+
|
|
9
|
+
## 1.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- Prepare the SDK for a stable release with expanded customer and vendor integration documentation.
|
|
14
|
+
|
|
3
15
|
## 0.2.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
6
18
|
|
|
19
|
+
- **BREAKING**: Session APIs are now vendor-scoped. The SDK uses `/api/v1/vendors/{slug}/sessions...` and no longer calls `/api/v1/sessions`.
|
|
20
|
+
- **BREAKING**: `CallpadConfig` now requires `audience: "agent" | "customer"`. Agent clients fetch vendor Pulse grants; customer clients fetch global user Pulse grants.
|
|
21
|
+
- **BREAKING**: Removed root `client.createSession`. Use `client.sessions.create(...)` for agent-originated sessions and `client.sessions.request(...)` for customer-originated sessions.
|
|
22
|
+
- Added customer session request flow with default auto-join and `useSessionRequest()`.
|
|
23
|
+
- Added `session.updated` client event and React hook invalidation for session upserts.
|
|
7
24
|
- **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
25
|
- **BREAKING**: `Session.telephonyLegs` is now derived from `participant.operations[]` where `kind === "sip_leg"`. The backend no longer returns a top-level `telephonyLegs[]`.
|
|
9
26
|
- **BREAKING**: Participant lifecycle states changed. Removed `ringing`, `joining`. Added `accepted`, `declined`, `withdrawn`. New `SessionEventMap` keys: `participant.accepted`, `participant.declined`, `participant.withdrawn`.
|
|
10
27
|
- **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
28
|
- **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
|
|
29
|
+
- Added `Dispatch` surface for team fan-out with `session.dispatches` and events: `dispatch.started`, `dispatch.resolved`, `dispatch.cancelled`.
|
|
13
30
|
- Added `SessionRouteContext` accessible as `session.route`.
|
|
14
31
|
- Added `useSessionCapabilities(sessionId?)` returning a capability `Set` for self.
|
|
15
32
|
- Added `session.capabilitiesOf(participantId?)`.
|
package/README.md
CHANGED
|
@@ -1,230 +1,347 @@
|
|
|
1
1
|
# farvex
|
|
2
2
|
|
|
3
|
-
Web SDK for Callpad sessions
|
|
3
|
+
Web SDK for Callpad voice sessions. It wraps the generated session HTTP client,
|
|
4
|
+
Centrifugo realtime updates, and LiveKit re-exports. Calls history, notes,
|
|
5
|
+
wrap-up, billing, metrics, and recording playback should use the normal REST
|
|
6
|
+
API clients.
|
|
7
|
+
|
|
8
|
+
## Exports
|
|
9
|
+
|
|
10
|
+
| Entry | Purpose |
|
|
11
|
+
| ---------------- | ------------------------------------------- |
|
|
12
|
+
| `farvex` | Session client factories and SDK/API types. |
|
|
13
|
+
| `farvex/react` | React provider and focused session hooks. |
|
|
14
|
+
| `farvex/livekit` | LiveKit React/client re-exports. |
|
|
4
15
|
|
|
5
16
|
## Install
|
|
6
17
|
|
|
7
18
|
```sh
|
|
8
|
-
npm
|
|
9
|
-
# or
|
|
10
|
-
pnpm add farvex
|
|
11
|
-
# or
|
|
12
|
-
bun add farvex
|
|
19
|
+
npm install farvex livekit-client @livekit/components-react
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
`livekit-client`, `@livekit/components-react`, `react`, and `react-dom` are
|
|
23
|
+
peer dependencies. Install the LiveKit packages only in apps that render call
|
|
24
|
+
media.
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
The SDK needs these values from the app:
|
|
29
|
+
|
|
30
|
+
| Value | Description |
|
|
31
|
+
| -------- | ----------------------------------------------- |
|
|
32
|
+
| `apiUrl` | Base URL for the Callpad API. |
|
|
33
|
+
| `userId` | Callpad user ID for the signed-in app user. |
|
|
34
|
+
| `auth` | Function that returns a bearer token on demand. |
|
|
35
|
+
| `vendor` | Vendor slug. Required by host clients. |
|
|
36
|
+
|
|
37
|
+
`getAccessToken` is called before API requests and when realtime auth needs a
|
|
38
|
+
fresh token. Keep token minting in your app backend or existing auth layer; the
|
|
39
|
+
browser client should only receive a user-scoped token. The examples below
|
|
40
|
+
assume `getAccessToken` already exists in the app.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createSessionClient } from "farvex";
|
|
44
|
+
|
|
45
|
+
const callpad = createSessionClient({
|
|
46
|
+
apiUrl: "https://api.callpad.example.com",
|
|
47
|
+
userId: currentUser.id,
|
|
48
|
+
auth: {
|
|
49
|
+
getAccessToken,
|
|
50
|
+
onUnauthorized: () => {
|
|
51
|
+
auth.signOut();
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
16
56
|
|
|
17
|
-
|
|
18
|
-
|
|
57
|
+
Call `connect()` once when the voice UI mounts. This opens the realtime
|
|
58
|
+
connection and keeps `sessions`, `invites`, and `status` updated. Call
|
|
59
|
+
`disconnect()` when the user leaves the voice UI, or `dispose()` when the client
|
|
60
|
+
will not be reused.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await callpad.connect();
|
|
64
|
+
await callpad.sessions.sync();
|
|
19
65
|
```
|
|
20
66
|
|
|
21
|
-
|
|
67
|
+
SDK methods throw `CallpadError` for API problem responses. They do not return a
|
|
68
|
+
custom result wrapper.
|
|
22
69
|
|
|
23
|
-
|
|
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. |
|
|
70
|
+
## Customer App
|
|
30
71
|
|
|
31
|
-
|
|
72
|
+
Customer apps use the common session client:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { createSessionClient } from "farvex";
|
|
76
|
+
|
|
77
|
+
const callpad = createSessionClient({
|
|
78
|
+
apiUrl: "https://api.callpad.example.com",
|
|
79
|
+
userId: customer.id,
|
|
80
|
+
auth: { getAccessToken },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await callpad.connect();
|
|
84
|
+
|
|
85
|
+
const started = await callpad.sessions.start({
|
|
86
|
+
vendor: "acme",
|
|
87
|
+
target: { type: "vendor_user", userId: 42 },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!started.join) {
|
|
91
|
+
throw new Error("This session did not return a media grant");
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`started.session` is the current `VoiceSession`. `started.join` is a LiveKit
|
|
96
|
+
grant for the caller when the current user should join the media room.
|
|
97
|
+
|
|
98
|
+
### Customer Calls A Vendor User
|
|
99
|
+
|
|
100
|
+
This example starts an app-to-app call from a customer to a specific vendor user
|
|
101
|
+
and renders the audio room after the session is created.
|
|
32
102
|
|
|
33
103
|
```tsx
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} from "farvex/react";
|
|
104
|
+
"use client";
|
|
105
|
+
|
|
106
|
+
import { useMemo, useState } from "react";
|
|
107
|
+
import { createSessionClient, type SessionClient, type VoiceJoinGrant } from "farvex";
|
|
108
|
+
import { CallpadProvider, useCallpad } from "farvex/react";
|
|
109
|
+
import { LiveKitRoom, RoomAudioRenderer } from "farvex/livekit";
|
|
110
|
+
|
|
111
|
+
const vendor = "acme";
|
|
112
|
+
const vendorUserId = 42;
|
|
113
|
+
|
|
114
|
+
export const CustomerVoice = ({ customerId }: { customerId: number }) => {
|
|
115
|
+
const client = useMemo<SessionClient>(
|
|
116
|
+
() =>
|
|
117
|
+
createSessionClient({
|
|
118
|
+
apiUrl: process.env.NEXT_PUBLIC_CALLPAD_API_URL!,
|
|
119
|
+
userId: customerId,
|
|
120
|
+
auth: { getAccessToken },
|
|
121
|
+
}),
|
|
122
|
+
[customerId],
|
|
123
|
+
);
|
|
39
124
|
|
|
40
|
-
export default function App() {
|
|
41
125
|
return (
|
|
42
|
-
<CallpadProvider
|
|
43
|
-
|
|
44
|
-
apiBaseUrl: "https://api.callpad.example.com",
|
|
45
|
-
vendor: "acme",
|
|
46
|
-
auth: { getAccessToken: async () => myToken() },
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<CallScreen />
|
|
126
|
+
<CallpadProvider client={client} connect disposeOnUnmount>
|
|
127
|
+
<CustomerCallButton />
|
|
50
128
|
</CallpadProvider>
|
|
51
129
|
);
|
|
52
|
-
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const CustomerCallButton = () => {
|
|
133
|
+
const callpad = useCallpad();
|
|
134
|
+
const [join, setJoin] = useState<VoiceJoinGrant | null>(null);
|
|
135
|
+
|
|
136
|
+
const startCall = async () => {
|
|
137
|
+
const started = await callpad.sessions.start({
|
|
138
|
+
vendor,
|
|
139
|
+
target: { type: "vendor_user", userId: vendorUserId },
|
|
140
|
+
});
|
|
141
|
+
setJoin(started.join);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<button type="button" onClick={startCall}>
|
|
147
|
+
Call support
|
|
148
|
+
</button>
|
|
149
|
+
{join ? <SessionMedia join={join} /> : null}
|
|
150
|
+
</>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const SessionMedia = ({ join }: { join: VoiceJoinGrant }) => (
|
|
155
|
+
<LiveKitRoom serverUrl={join.url} token={join.token} audio video={false}>
|
|
156
|
+
<RoomAudioRenderer />
|
|
157
|
+
</LiveKitRoom>
|
|
158
|
+
);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Vendor App
|
|
162
|
+
|
|
163
|
+
Vendor-side apps use the host client. Host clients can start calls to customers,
|
|
164
|
+
contacts, and phone numbers, and can use elevated controls such as recording and
|
|
165
|
+
ending a session.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { createHostClient } from "farvex";
|
|
169
|
+
|
|
170
|
+
const callpad = createHostClient({
|
|
171
|
+
apiUrl: "https://api.callpad.example.com",
|
|
172
|
+
vendor: "acme",
|
|
173
|
+
userId: user.id,
|
|
174
|
+
auth: { getAccessToken },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const started = await callpad.sessions.start({
|
|
178
|
+
target: { type: "customer", userId: customer.id },
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await callpad.host.startRecording(started.session.id);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Receiving Customer Calls
|
|
53
185
|
|
|
54
|
-
|
|
186
|
+
When a customer calls a vendor user, the vendor app receives an incoming invite
|
|
187
|
+
over realtime. Accepting the invite returns a join grant for the vendor user.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
"use client";
|
|
191
|
+
|
|
192
|
+
import { useState } from "react";
|
|
193
|
+
import { type VoiceJoinGrant } from "farvex";
|
|
194
|
+
import { useCallpad, useIncomingInvite } from "farvex/react";
|
|
195
|
+
import { LiveKitRoom, RoomAudioRenderer } from "farvex/livekit";
|
|
196
|
+
|
|
197
|
+
export const IncomingCall = () => {
|
|
198
|
+
const callpad = useCallpad();
|
|
55
199
|
const invite = useIncomingInvite();
|
|
56
|
-
const
|
|
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
|
-
);
|
|
200
|
+
const [join, setJoin] = useState<VoiceJoinGrant | null>(null);
|
|
201
|
+
|
|
202
|
+
if (!invite) {
|
|
203
|
+
return null;
|
|
66
204
|
}
|
|
67
205
|
|
|
206
|
+
const accept = async () => {
|
|
207
|
+
const started = await callpad.sessions.accept({
|
|
208
|
+
sessionId: invite.session.id,
|
|
209
|
+
participantId: invite.participant.id,
|
|
210
|
+
});
|
|
211
|
+
setJoin(started.join);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const reject = () =>
|
|
215
|
+
callpad.sessions.reject({
|
|
216
|
+
sessionId: invite.session.id,
|
|
217
|
+
participantId: invite.participant.id,
|
|
218
|
+
});
|
|
219
|
+
|
|
68
220
|
return (
|
|
69
|
-
|
|
70
|
-
<button
|
|
71
|
-
|
|
72
|
-
</button>
|
|
73
|
-
<button
|
|
74
|
-
disabled={!controls.recording.enabled}
|
|
75
|
-
onClick={controls.recording.toggle}
|
|
76
|
-
>
|
|
77
|
-
{controls.recording.active ? "Stop recording" : "Record"}
|
|
221
|
+
<>
|
|
222
|
+
<button type="button" onClick={accept}>
|
|
223
|
+
Answer
|
|
78
224
|
</button>
|
|
79
|
-
<button
|
|
80
|
-
|
|
225
|
+
<button type="button" onClick={reject}>
|
|
226
|
+
Decline
|
|
81
227
|
</button>
|
|
82
|
-
|
|
228
|
+
{join ? (
|
|
229
|
+
<LiveKitRoom serverUrl={join.url} token={join.token} audio video={false}>
|
|
230
|
+
<RoomAudioRenderer />
|
|
231
|
+
</LiveKitRoom>
|
|
232
|
+
) : null}
|
|
233
|
+
</>
|
|
83
234
|
);
|
|
84
|
-
}
|
|
235
|
+
};
|
|
85
236
|
```
|
|
86
237
|
|
|
87
|
-
|
|
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.
|
|
140
|
-
|
|
141
|
-
## Local development
|
|
238
|
+
### Host Controls
|
|
142
239
|
|
|
143
|
-
|
|
240
|
+
```ts
|
|
241
|
+
const sessionId = started.session.id;
|
|
144
242
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
243
|
+
await callpad.host.addParticipant({
|
|
244
|
+
sessionId,
|
|
245
|
+
target: { type: "phone", phone: "+15551234567" },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await callpad.host.startRecording(sessionId);
|
|
249
|
+
await callpad.host.stopRecording(sessionId);
|
|
250
|
+
await callpad.host.end(sessionId);
|
|
151
251
|
```
|
|
152
252
|
|
|
153
|
-
|
|
253
|
+
Use `sessions.can(session, action)` or the React `useCan(id, action)` hook to
|
|
254
|
+
gate controls before calling actions such as `accept`, `leave`, `end`, or
|
|
255
|
+
`startRecording`.
|
|
154
256
|
|
|
155
|
-
|
|
156
|
-
# start the backend first: `bun run docker:up && docker compose up callpad.http`
|
|
157
|
-
bun run generate:api # writes src/api/generated/
|
|
158
|
-
```
|
|
257
|
+
## React
|
|
159
258
|
|
|
160
|
-
|
|
259
|
+
Import `farvex/react` only from client components.
|
|
161
260
|
|
|
162
|
-
|
|
261
|
+
```tsx
|
|
262
|
+
"use client";
|
|
163
263
|
|
|
164
|
-
|
|
264
|
+
import { useMemo, type ReactNode } from "react";
|
|
265
|
+
import { createHostClient, type HostClient } from "farvex";
|
|
266
|
+
import {
|
|
267
|
+
CallpadProvider,
|
|
268
|
+
useCallpad,
|
|
269
|
+
useIncomingInvite,
|
|
270
|
+
useSession,
|
|
271
|
+
useStatus,
|
|
272
|
+
} from "farvex/react";
|
|
165
273
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
274
|
+
export const VoiceProvider = ({ children }: { children: ReactNode }) => {
|
|
275
|
+
const client = useMemo<HostClient>(
|
|
276
|
+
() =>
|
|
277
|
+
createHostClient({
|
|
278
|
+
apiUrl: process.env.NEXT_PUBLIC_CALLPAD_API_URL!,
|
|
279
|
+
vendor: "acme",
|
|
280
|
+
userId: 42,
|
|
281
|
+
auth: { getAccessToken },
|
|
282
|
+
}),
|
|
283
|
+
[],
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<CallpadProvider client={client} connect disposeOnUnmount>
|
|
288
|
+
{children}
|
|
289
|
+
</CallpadProvider>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
170
292
|
```
|
|
171
293
|
|
|
172
|
-
|
|
294
|
+
Hooks:
|
|
173
295
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
296
|
+
| Hook | Purpose |
|
|
297
|
+
| ------------------------- | ------------------------------------------------- |
|
|
298
|
+
| `useCallpad()` | Current SDK client. |
|
|
299
|
+
| `useStatus()` | Realtime connection status. |
|
|
300
|
+
| `useSessions()` | Visible live sessions. |
|
|
301
|
+
| `useSession(id?)` | Specific session, or active session when omitted. |
|
|
302
|
+
| `useIncomingInvites()` | Pending invites for the current user. |
|
|
303
|
+
| `useIncomingInvite()` | First pending invite. |
|
|
304
|
+
| `useCan(id, action)` | UI helper for session actions. |
|
|
305
|
+
| `useSessionDuration(id?)` | Live elapsed time. |
|
|
178
306
|
|
|
179
|
-
|
|
307
|
+
Session participants and metadata are exposed on `VoiceSession.participants`.
|
|
308
|
+
Use LiveKit hooks for media participants, tracks, mute state, speaking state,
|
|
309
|
+
device selection, and room lifecycle.
|
|
180
310
|
|
|
181
|
-
|
|
182
|
-
# inside websdk/
|
|
183
|
-
npx yalc push # push built dist/ to subscribers
|
|
184
|
-
```
|
|
311
|
+
## LiveKit
|
|
185
312
|
|
|
186
|
-
|
|
313
|
+
`farvex/livekit` re-exports first-party LiveKit primitives:
|
|
187
314
|
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
|
|
315
|
+
```tsx
|
|
316
|
+
import { LiveKitRoom, RoomAudioRenderer } from "farvex/livekit";
|
|
317
|
+
import type { VoiceJoinGrant } from "farvex";
|
|
318
|
+
|
|
319
|
+
export const SessionMedia = ({ join }: { join: VoiceJoinGrant }) => (
|
|
320
|
+
<LiveKitRoom serverUrl={join.url} token={join.token} audio video={false}>
|
|
321
|
+
<RoomAudioRenderer />
|
|
322
|
+
</LiveKitRoom>
|
|
323
|
+
);
|
|
191
324
|
```
|
|
192
325
|
|
|
193
|
-
##
|
|
326
|
+
## Development
|
|
327
|
+
|
|
328
|
+
From `websdk/`:
|
|
194
329
|
|
|
195
330
|
```sh
|
|
196
|
-
bun run
|
|
197
|
-
bun run
|
|
198
|
-
|
|
199
|
-
bun run release # builds, runs changeset publish
|
|
200
|
-
git push --follow-tags
|
|
331
|
+
bun run generate
|
|
332
|
+
bun run check
|
|
333
|
+
bun run build
|
|
201
334
|
```
|
|
202
335
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
## Package conventions
|
|
336
|
+
HTTP calls must go through the generated Hey API client in `src/api/generated/`.
|
|
337
|
+
Do not hand-edit generated files.
|
|
206
338
|
|
|
207
|
-
|
|
208
|
-
- No top-level access to `window`, `document`, `navigator`, `RTCPeerConnection`, or `localStorage`. Any such access happens inside methods or effects.
|
|
209
|
-
- No `any` or `unknown` in exported types.
|
|
210
|
-
- Tests live next to source (`foo.test.ts` beside `foo.ts`).
|
|
211
|
-
- `engines.node >= 18`.
|
|
339
|
+
## Release
|
|
212
340
|
|
|
213
|
-
|
|
341
|
+
Changesets lives in `websdk/.changeset`. From `websdk/`:
|
|
214
342
|
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
226
|
-
dist/ build output (gitignored)
|
|
227
|
-
tsup.config.ts build config (ESM + CJS + dts, use-client preserved)
|
|
228
|
-
tsconfig.json strict TS, bundler resolution
|
|
229
|
-
vitest.config.ts jsdom test env (includes src/**/*.test.ts)
|
|
343
|
+
```sh
|
|
344
|
+
bun run changeset
|
|
345
|
+
bun run version
|
|
346
|
+
bun run release
|
|
230
347
|
```
|