react-sip-kit 0.6.2 → 0.7.0-beta

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/README.md CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  # react-sip-kit
3
2
 
4
3
  A modern **React SIP.js toolkit** for building web softphones and SIP clients.
@@ -8,14 +7,14 @@ Supports **audio/video calls**, **call recording**, **screen sharing**, and **de
8
7
 
9
8
  ## ✨ Features
10
9
 
11
- - 📞 **Audio & Video Calls** — automatic device detection
12
- - 🎥 **Video Support** — manage local & remote streams seamlessly
13
- - 🔴 **Call Recording** — audio/video recording built-in
10
+ - 📞 **Audio & Video Calls** — with device auto-detection
11
+ - 🎥 **Video Support** — render local & remote streams easily
12
+ - 🔴 **Call Recording** — audio/video capture
14
13
  - 🖥️ **Screen Sharing** — during video calls
15
- - 🎧 **Device Management** — select audio/video input & output devices
16
- - 🔄 **Multi-account & Multi-line Support** — handle multiple SIP accounts and concurrent calls
17
- - ⚡ **TypeScript-first** — type-safe, modular APIs
18
- - 🛠️ **Configurable & Extensible** — tailored to your SIP setup
14
+ - 🎧 **Device Management** — choose input/output devices
15
+ - 🔄 **Multi-Account & Multi-Line Support** — concurrent calls
16
+ - ⚡ **TypeScript-first API** — safe and clean
17
+ - 🛠️ **Configurable & Extensible**
19
18
 
20
19
  ---
21
20
 
@@ -29,11 +28,9 @@ yarn add react-sip-kit
29
28
 
30
29
  ---
31
30
 
32
- ## 🚀 Getting Started
33
-
34
- ### 1️⃣ Initialize a `SipManager`
31
+ ## 🚀 Quick Start
35
32
 
36
- Create a single `SipManager` instance. Add accounts dynamically as needed.
33
+ ### 1️⃣ Create a SipManager Instance
37
34
 
38
35
  ```tsx
39
36
  // main.tsx
@@ -47,6 +44,7 @@ export const SipConnection = new SipManager();
47
44
  const Providers = () => {
48
45
  const configs = [
49
46
  {
47
+ key: 'support-01', // Optional → If omitted, defaults to account.username
50
48
  account: {
51
49
  domain: 'sip.example.com',
52
50
  username: '1010',
@@ -60,13 +58,11 @@ const Providers = () => {
60
58
 
61
59
  useEffect(() => {
62
60
  configs.forEach((config) => SipConnection.add(config));
63
- }, [configs]);
61
+ }, []);
64
62
 
65
63
  return (
66
64
  <StrictMode>
67
- {configs.map((config) => (
68
- <App key={config.account.username} username={config.account.username} />
69
- ))}
65
+ <App />
70
66
  </StrictMode>
71
67
  );
72
68
  };
@@ -76,109 +72,71 @@ createRoot(document.getElementById('root')!).render(<Providers />);
76
72
 
77
73
  ---
78
74
 
79
- ### 2️⃣ Access SIP state & methods
75
+ ## 🔑 Resolving Accounts & Sessions
80
76
 
81
- Fetch **methods** and **account state** via username, lineKey, or remoteNumber:
77
+ All lookups use one of the following keys:
82
78
 
83
- ```tsx
84
- // App.tsx
85
- import { SipConnection } from './main';
86
-
87
- function App({ username }: { username: string }) {
88
- const { dialByNumber } = SipConnection.getSessionMethodsBy({ username });
89
- const { watch, status, lines } = SipConnection.getAccountBy({ username });
79
+ | Key | Purpose | Example |
80
+ | -------------- | ------------------------------------------------------------- | --------------------------------------------------- |
81
+ | `configKey` | Refers to a specific SIP account instance | `{ configKey: 'support-01' }` |
82
+ | `lineKey` | Refers to an **active call** / line | `{ lineKey: 1 }` |
83
+ | `remoteNumber` | Refers to peer phone number — must be paired with `configKey` | `{ configKey: 'support-01', remoteNumber: '1001' }` |
90
84
 
91
- const account = watch();
85
+ ---
92
86
 
93
- return (
94
- <>
95
- <h2>Web Phone {username} — {status}</h2>
96
- <button onClick={() => dialByNumber('audio', '1012')}>Call 1012</button>
97
- <button onClick={() => dialByNumber('video', '1012')}>Video Call 1012</button>
98
- </>
99
- );
100
- }
101
- ```
87
+ ### 2️⃣ Access Methods (Dial, Hold, Mute, etc.)
102
88
 
103
- You can also use **lineKey** or **remoteNumber** instead of username:
89
+ ```tsx
90
+ // Using configKey
91
+ const { dialByNumber } = SipConnection.getSessionMethodsBy({ configKey: 'support-01' });
92
+ dialByNumber('audio', '1002');
104
93
 
105
- ```ts
106
- SipConnection.getSessionMethodsBy({ lineKey: 1 });
107
- SipConnection.getAccountBy({ remoteNumber: '1001' });
94
+ // Using lineKey (after a call is active)
95
+ SipConnection.getSessionMethodsBy({ lineKey: 1 }).hold();
108
96
  ```
109
97
 
110
98
  ---
111
99
 
112
- ### 3️⃣ Watch session data (`useWatchSessionData`)
113
-
114
- Fine-grained updates for a line/session:
100
+ ### 3️⃣ Read Account State (Reactive)
115
101
 
116
102
  ```tsx
117
- import { useWatchSessionData } from 'react-sip-kit';
118
-
119
- function RecordingStatus({ lineKey }: { lineKey: number }) {
120
- const isRecording = useWatchSessionData({
121
- key: { lineKey },
122
- name: 'recordMedia.recording',
123
- });
124
-
125
- return <p>Recording: {isRecording ? 'Yes' : 'No'}</p>;
126
- }
103
+ const { watch, status } = SipConnection.getAccountBy({ configKey: 'support-01' });
104
+ const account = watch(); // contains lines, registration, media devices, etc.
127
105
  ```
128
106
 
129
- Watch multiple fields:
107
+ ---
108
+
109
+ ### 4️⃣ Track Session State (`useWatchSessionData`)
130
110
 
131
111
  ```tsx
132
- const [localMediaStatus, isRecording] = useWatchSessionData({
112
+ import { useWatchSessionData } from 'react-sip-kit';
113
+
114
+ const isRecording = useWatchSessionData({
133
115
  key: { lineKey: 1 },
134
- name: ['localMediaStreamStatus', 'recordMedia.recording'],
116
+ name: 'recordMedia.recording',
135
117
  });
136
- ```
137
118
 
138
- Works with `remoteNumber` as well:
139
-
140
- ```tsx
141
- const isMuted = useWatchSessionData({
142
- key: { remoteNumber: '1001' },
143
- name: 'localMediaStreamStatus.muted',
119
+ const [mediaStatus, isMuted] = useWatchSessionData({
120
+ key: { configKey: 'support-01', remoteNumber: '1002' },
121
+ name: ['localMediaStreamStatus', 'localMediaStreamStatus.muted'],
144
122
  });
145
123
  ```
146
- ---
147
124
 
148
- ### 4️⃣ Helper Resolver Methods
125
+ ---
149
126
 
150
- `SipManager` provides convenient **resolver methods** for quickly looking up lines, sessions, or usernames by `lineKey` or `remoteNumber`. These help avoid manually mapping usernames or tracking lines.
127
+ ### 5️⃣ Resolver Helper Methods
151
128
 
152
129
  ```ts
153
- import { SipConnection } from './main';
154
-
155
- // Get a specific line
156
- const line = SipConnection.getLineBy({ lineKey: 1 });
157
- // or by remote number
158
- const remoteLine = SipConnection.getLineBy({ remoteNumber: '1001' });
159
-
160
- // Get an active SIP session
161
- const session = SipConnection.getSessionBy({ lineKey: 1 });
162
- // or by remote number
163
- const remoteSession = SipConnection.getSessionBy({ remoteNumber: '1001' });
164
-
165
- // Get the username for a line
166
- const username = SipConnection.getUsernameBy({ lineKey: 1 });
167
- // or by remote number
168
- const remoteUsername = SipConnection.getUsernameBy({ remoteNumber: '1001' });
169
- ```
130
+ SipConnection.getLineBy({ lineKey: 1 });
131
+ SipConnection.getLineBy({ configKey: 'support-01', remoteNumber: '1002' });
170
132
 
171
- **Notes:**
172
-
173
- * These methods are **lookup helpers only** — they do not return reactive data.
174
- * Combine with `getAccountBy()` or `useWatchSessionData()` for reactive state.
175
- * Useful when you have only a `lineKey` or `remoteNumber` but need the username or session quickly.
133
+ SipConnection.getSessionBy({ lineKey: 1 });
134
+ SipConnection.getUsernameBy({ configKey: 'support-01', remoteNumber: '1002' });
135
+ ```
176
136
 
177
137
  ---
178
138
 
179
- ### 5️⃣ Render media streams
180
-
181
- Media components are line-bound:
139
+ ### 6️⃣ Render Media Streams
182
140
 
183
141
  ```tsx
184
142
  import { VideoStream, AudioStream } from 'react-sip-kit';
@@ -191,12 +149,50 @@ import { VideoStream, AudioStream } from 'react-sip-kit';
191
149
 
192
150
  ---
193
151
 
194
- ## ⚙️ Configuration
152
+ ### 🧠 Managing Config Keys & Dynamic Rendering
153
+
154
+ ```tsx
155
+ const configs = SipConnection.useWatchConfigs();
156
+ // → [{ key: 'support-01', account: {...} }, { key: 'sales-02', ... }]
157
+ ```
158
+
159
+ ✅ **When to use `useWatchConfigs()`**
160
+
161
+ | Scenario | Should you use it? | Reason |
162
+ | ------------------------------------------------------ | ----------------------- | ------------------------------------------------------------------------------- |
163
+ | Static single account | ❌ Not required | `configKey` is constant |
164
+ | You manually track configs in your own state | ❌ Optional | You already control the list |
165
+ | **Rendering UI for *each* config account dynamically** | ✅ **Yes — recommended** | Ensures you always use the correct `configKey`, even if keys are auto-generated |
166
+ | **Configs can be added or removed at runtime** | ✅ **Always** | Prevents mismatched lookups or stale references |
167
+
168
+ > **✔ Best Practice:**
169
+ > Always call `useWatchConfigs()` when rendering **lists of lines or accounts**, to ensure that each component receives the correct and *current* `configKey`.
170
+
171
+ ---
172
+
173
+ Example (Rendering a phone UI for *every* config dynamically):
174
+
175
+ ```tsx
176
+ const AccountsUI = () => {
177
+ const configs = SipConnection.useWatchConfigs();
178
+
179
+ return (
180
+ <>
181
+ {configs.map((config) => (
182
+ <Lines key={config.key} configKey={config.key} />
183
+ ))}
184
+ </>
185
+ );
186
+ };
187
+ ```
188
+
189
+ ---
195
190
 
196
- Each SIP account supports account, media, and feature settings:
191
+ ## ⚙️ Configuration Schema
197
192
 
198
193
  ```ts
199
194
  {
195
+ key?: string; // optional → falls back to account.username
200
196
  account: {
201
197
  domain: 'your.sip.domain',
202
198
  username: 'user',
@@ -205,38 +201,41 @@ Each SIP account supports account, media, and feature settings:
205
201
  webSocketPort: '8089',
206
202
  serverPath: '/ws',
207
203
  },
208
- features: { enableVideo: true },
209
- media: {
210
- audioInputDeviceId: 'default',
211
- audioOutputDeviceId: 'default',
212
- videoInputDeviceId: 'default',
204
+ features?: { enableVideo?: boolean },
205
+ media?: {
206
+ audioInputDeviceId?: string;
207
+ audioOutputDeviceId?: string;
208
+ videoInputDeviceId?: string;
213
209
  },
214
- registration: { registerExpires: 3600 },
210
+ registration?: { registerExpires?: number },
215
211
  }
216
212
  ```
217
213
 
218
214
  ---
219
215
 
220
- ## 💡 Best Practices
216
+ ## Best Practices
221
217
 
222
- * Use `username`, `lineKey`, or `remoteNumber` to fetch methods/state.
223
- * `.watch()` provides reactive account data (`lines`, `status`).
224
- * `useWatchSessionData` tracks **line-specific updates** (mute, hold, video, recording, etc.).
225
- * Render `<VideoStream>` & `<AudioStream>` only for active calls.
226
- * Manage mic/camera permissions upfront.
227
- * Add accounts dynamically with `SipConnection.add(config)`.
218
+ | Task | Recommended Approach |
219
+ | ---------------------- | --------------------------------- |
220
+ | Reference active call | Use `lineKey` when available |
221
+ | Reference account | Use `configKey` |
222
+ | Lookup by phone number | Use `{ configKey, remoteNumber }` |
223
+ | Track config keys | Use `useWatchConfigs()` |
224
+ | Watch configs state | `useWatchConfigs()` |
225
+ | Watch account state | `getAccountBy().watch()` |
226
+ | Watch session props | `useWatchSessionData()` |
228
227
 
229
228
  ---
230
229
 
231
- ## 🧑‍💻 Examples
230
+ ## 📂 Examples
232
231
 
233
232
  Check [`/example`](https://github.com/shervin-ghajar/react-sip-kit/tree/main/example) for:
234
233
 
235
- * Multi-account setups
236
- * Audio & video calls
237
- * Hold, mute, attended transfer
234
+ * Multi-account softphone
235
+ * Audio/video calls
236
+ * Hold / mute / attended transfer
238
237
  * Call recording & screen sharing
239
- * Local & remote media rendering
238
+ * Stream rendering
240
239
 
241
240
  ---
242
241
 
@@ -250,6 +249,6 @@ MIT
250
249
 
251
250
  **Shervin Ghajar**
252
251
 
253
- * GitHub: [@shervin-ghajar](https://github.com/shervin-ghajar)
254
- * NPM: [react-sip-kit](https://www.npmjs.com/package/react-sip-kit)
255
- * Repository: [react-sip-kit](https://github.com/shervin-ghajar/react-sip-kit)
252
+ * GitHub: [https://github.com/shervin-ghajar](https://github.com/shervin-ghajar)
253
+ * NPM: [https://www.npmjs.com/package/react-sip-kit](https://www.npmjs.com/package/react-sip-kit)
254
+ * Repository: [https://github.com/shervin-ghajar/react-sip-kit](https://github.com/shervin-ghajar/react-sip-kit)
@@ -1,4 +1,4 @@
1
- import { SipAccountConfig, SipAdvancedConfig, SipConfigs, SipFeaturesConfig, SipMediaConfig, SipPermissionsConfig, SipPolicyConfig, SipRecordingConfig, SipRegistrationConfig, SipStorageConfig, SipXmppConfig } from './types';
1
+ import { SipAccountConfig, SipAdvancedConfig, SipConfigs, SipFeaturesConfig, SipMediaConfig, SipPolicyConfig, SipRecordingConfig, SipRegistrationConfig, SipStorageConfig } from './types';
2
2
  export declare const defaultAccountConfig: SipAccountConfig;
3
3
  export declare const defaultFeaturesConfig: SipFeaturesConfig;
4
4
  export declare const defaultMediaConfig: SipMediaConfig;
@@ -7,6 +7,4 @@ export declare const defaultRegistrationConfig: SipRegistrationConfig;
7
7
  export declare const defaultStorageConfig: SipStorageConfig;
8
8
  export declare const defaultRecordingConfig: SipRecordingConfig;
9
9
  export declare const defaultAdvancedConfig: SipAdvancedConfig;
10
- export declare const defaultXmppConfig: SipXmppConfig;
11
- export declare const defaultPermissionsConfig: SipPermissionsConfig;
12
10
  export declare const defaultSipConfigs: SipConfigs;
@@ -1,4 +1,5 @@
1
1
  export interface SipConfigs {
2
+ key: string;
2
3
  account: SipAccountConfig;
3
4
  features: SipFeaturesConfig;
4
5
  media: SipMediaConfig;
@@ -7,17 +8,6 @@ export interface SipConfigs {
7
8
  storage: SipStorageConfig;
8
9
  recording: SipRecordingConfig;
9
10
  advanced: SipAdvancedConfig;
10
- xmpp: SipXmppConfig;
11
- permissions: SipPermissionsConfig;
12
- }
13
- export interface SipPermissionsConfig {
14
- enableSendFiles: boolean;
15
- enableSendImages: boolean;
16
- enableAudioRecording: boolean;
17
- enableVideoRecording: boolean;
18
- enableSms: boolean;
19
- enableFax: boolean;
20
- enableEmail: boolean;
21
11
  }
22
12
  export interface SipXmppConfig {
23
13
  server: string;
@@ -90,16 +80,8 @@ export interface SipMediaConfig {
90
80
  }
91
81
  export interface SipFeaturesConfig {
92
82
  enableVideo: boolean;
93
- enableRingtone: boolean;
94
- enableTextMessaging: boolean;
95
83
  enableTransfer: boolean;
96
84
  enableConference: boolean;
97
- enableTextExpressions: boolean;
98
- enableTextDictate: boolean;
99
- enableAlphanumericDial: boolean;
100
- enableAccountSettings: boolean;
101
- enableAppearanceSettings: boolean;
102
- enableNotificationSettings: boolean;
103
85
  }
104
86
  export interface SipAccountConfig {
105
87
  username: string;
@@ -1,11 +1,12 @@
1
- import { SipAccountConfig } from '../configs/types';
1
+ import { SipAccountConfig, SipConfigs } from '../configs/types';
2
2
  import { LineType, SipInvitationType } from '../store/types';
3
3
  export declare class Line implements LineType {
4
- lineKey: number;
4
+ configKey: SipConfigs['key'];
5
+ lineKey: LineType['lineKey'];
5
6
  remoteNumber: string;
6
7
  username: string;
7
8
  sipSession: SipInvitationType | null;
8
9
  localSoundMeter: any;
9
10
  remoteSoundMeter: any;
10
- constructor(username: SipAccountConfig['username'], lineKey: number, remoteNumber: string);
11
+ constructor(configKey: SipConfigs['key'], username: SipAccountConfig['username'], lineKey: LineType['lineKey'], remoteNumber: string);
11
12
  }
@@ -1,16 +1,16 @@
1
- import { SipAccountConfig } from '../../configs/types';
1
+ import { SipConfigs } from '../../configs/types';
2
2
  import { SipUserAgent } from '../../types';
3
3
  /**
4
4
  * Called when account is registered
5
5
  */
6
- export declare function onRegistered(username: SipAccountConfig['username'], userAgent: SipUserAgent): void;
6
+ export declare function onRegistered(configKey: SipConfigs['key'], userAgent: SipUserAgent): void;
7
7
  /**
8
8
  * Called if UserAgent can connect, but not register.
9
9
  * @param {string} response Incoming request message
10
10
  * @param {string} cause Cause message. Unused
11
11
  **/
12
- export declare function onRegisterFailed(username: SipAccountConfig['username'], response: any, cause: any): void;
12
+ export declare function onRegisterFailed(configKey: SipConfigs['key'], response: any, cause: any): void;
13
13
  /**
14
14
  * Called when Unregister is requested
15
15
  */
16
- export declare function onUnregistered(username: SipAccountConfig['username'], userAgent: SipUserAgent): void;
16
+ export declare function onUnregistered(configKey: SipConfigs['key'], userAgent: SipUserAgent): void;
@@ -1,10 +1,10 @@
1
- import { SipAccountConfig } from '../../configs/types';
1
+ import { SipConfigs } from '../../configs/types';
2
2
  import { LineType, SipSessionDescriptionHandler, SipSessionType } from '../../store/types';
3
3
  import { CallbackFunction } from '../../types';
4
4
  import { Bye, Message } from 'sip.js';
5
5
  import { IncomingRequestMessage, IncomingResponse } from 'sip.js/lib/core';
6
- export declare const sessionEvents: ({ username }: {
7
- username: SipAccountConfig["username"];
6
+ export declare const sessionEvents: ({ configKey }: {
7
+ configKey: SipConfigs["key"];
8
8
  }) => {
9
9
  onInviteCancel: (lineObj: LineType, response: IncomingRequestMessage, callback?: CallbackFunction<any>) => void;
10
10
  onInviteAccepted: (lineObj: LineType, videoEnabled: boolean, response?: IncomingResponse) => Promise<void>;
@@ -1,6 +1,6 @@
1
- import { SipAccountConfig } from '../../configs/types';
1
+ import { SipConfigs } from '../../configs/types';
2
2
  import { SipUserAgent } from '../../types';
3
- export declare function onTransportConnected(username: SipAccountConfig['username'], userAgent?: SipUserAgent | null): void;
4
- export declare function onTransportConnectError(error: Error, username: SipAccountConfig['username'], userAgent?: SipUserAgent | null): void;
5
- export declare function onTransportDisconnected(username: SipAccountConfig['username'], userAgent: SipUserAgent): void;
6
- export declare function reconnectTransport(username: SipAccountConfig['username'], userAgent?: SipUserAgent | null): void;
3
+ export declare function onTransportConnected(configKey: SipConfigs['key'], userAgent?: SipUserAgent | null): void;
4
+ export declare function onTransportConnectError(error: Error, configKey: SipConfigs['key'], userAgent?: SipUserAgent | null): void;
5
+ export declare function onTransportDisconnected(configKey: SipConfigs['key'], userAgent: SipUserAgent): void;
6
+ export declare function reconnectTransport(configKey: SipConfigs['key'], userAgent?: SipUserAgent | null, forceReconnect?: boolean): void;
@@ -1,7 +1,7 @@
1
- import { SipAccountConfig } from '../../configs/types';
1
+ import { SipConfigs } from '../../configs/types';
2
2
  import { LineType } from '../../store/types';
3
- export declare function useSipManager({ username }: {
4
- username: SipAccountConfig['username'];
3
+ export declare function useSipManager({ configKey }: {
4
+ configKey: SipConfigs['key'];
5
5
  }): () => {
6
6
  status: import("../../store/types").SipUserAgentStatus;
7
7
  lines: LineType[];
@@ -0,0 +1 @@
1
+ export declare const useWatchConfigs: () => import("../../configs/types").SipConfigs[];
@@ -1,3 +1,4 @@
1
+ import { SipConfigs } from '../../configs/types';
1
2
  import { LineType, SipSessionDataType } from '../../store/types';
2
3
  type Primitive = string | number | boolean | symbol | null | undefined;
3
4
  type Path<T> = {
@@ -49,6 +50,7 @@ export declare function useWatchSessionData(props: {
49
50
  export declare function useWatchSessionData(props: {
50
51
  key: {
51
52
  remoteNumber: SipSessionDataType['remoteNumber'];
53
+ configKey: SipConfigs['key'];
52
54
  };
53
55
  name?: undefined;
54
56
  }): SipSessionDataType;
@@ -61,6 +63,7 @@ export declare function useWatchSessionData<P extends Path<SipSessionDataType>>(
61
63
  export declare function useWatchSessionData<P extends Path<SipSessionDataType>>(props: {
62
64
  key: {
63
65
  remoteNumber: SipSessionDataType['remoteNumber'];
66
+ configKey: SipConfigs['key'];
64
67
  };
65
68
  name: P;
66
69
  }): PathValue<SipSessionDataType, P>;
@@ -75,6 +78,7 @@ export declare function useWatchSessionData<const P extends readonly Path<SipSes
75
78
  export declare function useWatchSessionData<const P extends readonly Path<SipSessionDataType>[]>(props: {
76
79
  key: {
77
80
  remoteNumber: SipSessionDataType['remoteNumber'];
81
+ configKey: SipConfigs['key'];
78
82
  };
79
83
  name: P;
80
84
  }): {