farvex 0.1.0 → 1.0.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 +33 -0
- package/README.md +296 -93
- package/dist/index.cjs +1423 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +1421 -3
- package/dist/index.js.map +1 -1
- package/dist/livekit/index.cjs +85 -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/react/index.cjs +117 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +20 -2
- package/dist/react/index.d.ts +20 -2
- package/dist/react/index.js +109 -3
- package/dist/react/index.js.map +1 -1
- package/dist/types-DhJEeeui.d.cts +206 -0
- package/dist/types-DhJEeeui.d.ts +206 -0
- package/package.json +64 -54
- package/dist/core/index.cjs +0 -8
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.d.cts +0 -3
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -6
- package/dist/core/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# farvex
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Prepare the SDK for a stable release with expanded customer and vendor integration documentation.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- **BREAKING**: Session APIs are now vendor-scoped. The SDK uses `/api/v1/vendors/{slug}/sessions...` and no longer calls `/api/v1/sessions`.
|
|
14
|
+
- **BREAKING**: `CallpadConfig` now requires `audience: "agent" | "customer"`. Agent clients fetch vendor Pulse grants; customer clients fetch global user Pulse grants.
|
|
15
|
+
- **BREAKING**: Removed root `client.createSession`. Use `client.sessions.create(...)` for agent-originated sessions and `client.sessions.request(...)` for customer-originated sessions.
|
|
16
|
+
- Added customer session request flow with default auto-join and `useSessionRequest()`.
|
|
17
|
+
- Added `session.updated` client event and React hook invalidation for session upserts.
|
|
18
|
+
- **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.
|
|
19
|
+
- **BREAKING**: `Session.telephonyLegs` is now derived from `participant.operations[]` where `kind === "sip_leg"`. The backend no longer returns a top-level `telephonyLegs[]`.
|
|
20
|
+
- **BREAKING**: Participant lifecycle states changed. Removed `ringing`, `joining`. Added `accepted`, `declined`, `withdrawn`. New `SessionEventMap` keys: `participant.accepted`, `participant.declined`, `participant.withdrawn`.
|
|
21
|
+
- **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.
|
|
22
|
+
- **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.
|
|
23
|
+
- Added `Dispatch` surface for team fan-out with `session.dispatches` and events: `dispatch.started`, `dispatch.resolved`, `dispatch.cancelled`.
|
|
24
|
+
- Added `SessionRouteContext` accessible as `session.route`.
|
|
25
|
+
- Added `useSessionCapabilities(sessionId?)` returning a capability `Set` for self.
|
|
26
|
+
- Added `session.capabilitiesOf(participantId?)`.
|
|
27
|
+
- Added `InviteManager.dropBySession` triggered on `session.remove` pulse messages so invites tied to ended sessions are reaped without waiting for per-invite removes.
|
|
28
|
+
- Tests now live beside source (`foo.test.ts` next to `foo.ts`).
|
|
29
|
+
- Regenerated API types against the rewritten session module (JSON-aggregate backend).
|
|
30
|
+
- Added `./livekit` and `./mock` subpath exports.
|
|
31
|
+
- Added `Session.mediaRoom` getter exposing the underlying `livekit-client` `Room`, so the React provider can share it with the LiveKit context.
|
|
32
|
+
- 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.
|
|
33
|
+
- Fixed: `SessionEffectsExecutor` DI binding was missing from `sessionModule`; webhook jobs failed silently with `No bindings found for service: "SessionEffectsExecutor"`.
|
|
34
|
+
- 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`).
|
|
35
|
+
|
|
3
36
|
## 0.1.0
|
|
4
37
|
|
|
5
38
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,144 +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.
|
|
4
7
|
|
|
5
|
-
|
|
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. |
|
|
6
15
|
|
|
7
16
|
## Install
|
|
8
17
|
|
|
9
18
|
```sh
|
|
10
|
-
npm
|
|
11
|
-
# or
|
|
12
|
-
pnpm add farvex
|
|
13
|
-
# or
|
|
14
|
-
bun add farvex
|
|
19
|
+
npm install farvex livekit-client @livekit/components-react
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
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.
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
pnpm add react react-dom
|
|
21
|
-
```
|
|
26
|
+
## Setup
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
The SDK needs these values from the app:
|
|
24
29
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
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.
|
|
30
41
|
|
|
31
42
|
```ts
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
});
|
|
35
55
|
```
|
|
36
56
|
|
|
37
|
-
|
|
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.
|
|
38
61
|
|
|
39
|
-
|
|
62
|
+
```ts
|
|
63
|
+
await callpad.connect();
|
|
64
|
+
await callpad.sessions.sync();
|
|
65
|
+
```
|
|
40
66
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- No `next.config.js` `transpilePackages` changes required.
|
|
67
|
+
SDK methods throw `CallpadError` for API problem responses. They do not return a
|
|
68
|
+
custom result wrapper.
|
|
44
69
|
|
|
45
|
-
##
|
|
70
|
+
## Customer App
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
- `react` and `react-dom` are `external` at build time; your app supplies them.
|
|
72
|
+
Customer apps use the common session client:
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
```ts
|
|
75
|
+
import { createSessionClient } from "farvex";
|
|
51
76
|
|
|
52
|
-
|
|
77
|
+
const callpad = createSessionClient({
|
|
78
|
+
apiUrl: "https://api.callpad.example.com",
|
|
79
|
+
userId: customer.id,
|
|
80
|
+
auth: { getAccessToken },
|
|
81
|
+
});
|
|
53
82
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|
|
60
93
|
```
|
|
61
94
|
|
|
62
|
-
|
|
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.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
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
|
+
);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<CallpadProvider client={client} connect disposeOnUnmount>
|
|
127
|
+
<CustomerCallButton />
|
|
128
|
+
</CallpadProvider>
|
|
129
|
+
);
|
|
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
|
+
```
|
|
63
160
|
|
|
64
|
-
|
|
161
|
+
## Vendor App
|
|
65
162
|
|
|
66
|
-
|
|
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.
|
|
67
166
|
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
bun run dev # keep running; watches src/ and rebuilds dist/
|
|
71
|
-
npx yalc publish --push # in another shell, first time
|
|
72
|
-
```
|
|
167
|
+
```ts
|
|
168
|
+
import { createHostClient } from "farvex";
|
|
73
169
|
|
|
74
|
-
|
|
170
|
+
const callpad = createHostClient({
|
|
171
|
+
apiUrl: "https://api.callpad.example.com",
|
|
172
|
+
vendor: "acme",
|
|
173
|
+
userId: user.id,
|
|
174
|
+
auth: { getAccessToken },
|
|
175
|
+
});
|
|
75
176
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
177
|
+
const started = await callpad.sessions.start({
|
|
178
|
+
target: { type: "customer", userId: customer.id },
|
|
179
|
+
});
|
|
80
180
|
|
|
81
|
-
|
|
181
|
+
await callpad.host.startRecording(started.session.id);
|
|
182
|
+
```
|
|
82
183
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
184
|
+
### Receiving Customer Calls
|
|
185
|
+
|
|
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();
|
|
199
|
+
const invite = useIncomingInvite();
|
|
200
|
+
const [join, setJoin] = useState<VoiceJoinGrant | null>(null);
|
|
201
|
+
|
|
202
|
+
if (!invite) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
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
|
+
|
|
220
|
+
return (
|
|
221
|
+
<>
|
|
222
|
+
<button type="button" onClick={accept}>
|
|
223
|
+
Answer
|
|
224
|
+
</button>
|
|
225
|
+
<button type="button" onClick={reject}>
|
|
226
|
+
Decline
|
|
227
|
+
</button>
|
|
228
|
+
{join ? (
|
|
229
|
+
<LiveKitRoom serverUrl={join.url} token={join.token} audio video={false}>
|
|
230
|
+
<RoomAudioRenderer />
|
|
231
|
+
</LiveKitRoom>
|
|
232
|
+
) : null}
|
|
233
|
+
</>
|
|
234
|
+
);
|
|
235
|
+
};
|
|
86
236
|
```
|
|
87
237
|
|
|
88
|
-
|
|
238
|
+
### Host Controls
|
|
89
239
|
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
240
|
+
```ts
|
|
241
|
+
const sessionId = started.session.id;
|
|
242
|
+
|
|
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);
|
|
93
251
|
```
|
|
94
252
|
|
|
95
|
-
|
|
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`.
|
|
256
|
+
|
|
257
|
+
## React
|
|
258
|
+
|
|
259
|
+
Import `farvex/react` only from client components.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
"use client";
|
|
263
|
+
|
|
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";
|
|
273
|
+
|
|
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
|
+
};
|
|
292
|
+
```
|
|
96
293
|
|
|
97
|
-
|
|
294
|
+
Hooks:
|
|
98
295
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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. |
|
|
306
|
+
|
|
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.
|
|
102
310
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
311
|
+
## LiveKit
|
|
312
|
+
|
|
313
|
+
`farvex/livekit` re-exports first-party LiveKit primitives:
|
|
314
|
+
|
|
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
|
+
);
|
|
108
324
|
```
|
|
109
325
|
|
|
110
|
-
|
|
326
|
+
## Development
|
|
327
|
+
|
|
328
|
+
From `websdk/`:
|
|
111
329
|
|
|
112
330
|
```sh
|
|
113
|
-
|
|
114
|
-
|
|
331
|
+
bun run generate
|
|
332
|
+
bun run check
|
|
115
333
|
bun run build
|
|
116
|
-
bunx publint
|
|
117
|
-
bun pm pack # inspect the tarball contents
|
|
118
334
|
```
|
|
119
335
|
|
|
120
|
-
|
|
336
|
+
HTTP calls must go through the generated Hey API client in `src/api/generated/`.
|
|
337
|
+
Do not hand-edit generated files.
|
|
121
338
|
|
|
122
|
-
##
|
|
339
|
+
## Release
|
|
123
340
|
|
|
124
|
-
|
|
125
|
-
- No top-level access to `window`, `document`, `navigator`, `RTCPeerConnection`, or `localStorage`. Any such access happens inside methods or effects.
|
|
126
|
-
- `typescript/no-explicit-any` is enforced by oxlint; we avoid `unknown` in exported types by convention.
|
|
127
|
-
- `engines.node >= 18`.
|
|
341
|
+
Changesets lives in `websdk/.changeset`. From `websdk/`:
|
|
128
342
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
src/
|
|
134
|
-
index.ts public entry (curated re-exports later)
|
|
135
|
-
core/index.ts framework-agnostic client
|
|
136
|
-
react/index.ts React hooks + providers
|
|
137
|
-
test/ vitest + jsdom smoke tests
|
|
138
|
-
dist/ build output (gitignored)
|
|
139
|
-
tsup.config.ts build config (ESM + CJS + dts, use-client preserved)
|
|
140
|
-
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)
|
|
343
|
+
```sh
|
|
344
|
+
bun run changeset
|
|
345
|
+
bun run version
|
|
346
|
+
bun run release
|
|
144
347
|
```
|