react-sip-kit 0.6.2 → 0.7.1-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,69 @@ 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
+ const isRecording = SipConnection.useWatchSessionData({
133
113
  key: { lineKey: 1 },
134
- name: ['localMediaStreamStatus', 'recordMedia.recording'],
114
+ name: 'recordMedia.recording',
135
115
  });
136
- ```
137
-
138
- Works with `remoteNumber` as well:
139
116
 
140
- ```tsx
141
- const isMuted = useWatchSessionData({
142
- key: { remoteNumber: '1001' },
143
- name: 'localMediaStreamStatus.muted',
117
+ const [mediaStatus, isMuted] = SipConnection.useWatchSessionData({
118
+ key: { configKey: 'support-01', remoteNumber: '1002' },
119
+ name: ['localMediaStreamStatus', 'localMediaStreamStatus.muted'],
144
120
  });
145
121
  ```
146
- ---
147
122
 
148
- ### 4️⃣ Helper Resolver Methods
123
+ ---
149
124
 
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.
125
+ ### 5️⃣ Resolver Helper Methods
151
126
 
152
127
  ```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
- ```
170
-
171
- **Notes:**
128
+ SipConnection.getLineBy({ lineKey: 1 });
129
+ SipConnection.getLineBy({ configKey: 'support-01', remoteNumber: '1002' });
172
130
 
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.
131
+ SipConnection.getSessionBy({ lineKey: 1 });
132
+ SipConnection.getUsernameBy({ configKey: 'support-01', remoteNumber: '1002' });
133
+ ```
176
134
 
177
135
  ---
178
136
 
179
- ### 5️⃣ Render media streams
180
-
181
- Media components are line-bound:
137
+ ### 6️⃣ Render Media Streams
182
138
 
183
139
  ```tsx
184
140
  import { VideoStream, AudioStream } from 'react-sip-kit';
@@ -191,12 +147,50 @@ import { VideoStream, AudioStream } from 'react-sip-kit';
191
147
 
192
148
  ---
193
149
 
194
- ## ⚙️ Configuration
150
+ ### 🧠 Managing Config Keys & Dynamic Rendering
151
+
152
+ ```tsx
153
+ const configs = SipConnection.useWatchConfigs();
154
+ // → [{ key: 'support-01', account: {...} }, { key: 'sales-02', ... }]
155
+ ```
156
+
157
+ ✅ **When to use `useWatchConfigs()`**
158
+
159
+ | Scenario | Should you use it? | Reason |
160
+ | ------------------------------------------------------ | ----------------------- | ------------------------------------------------------------------------------- |
161
+ | Static single account | ❌ Not required | `configKey` is constant |
162
+ | You manually track configs in your own state | ❌ Optional | You already control the list |
163
+ | **Rendering UI for *each* config account dynamically** | ✅ **Yes — recommended** | Ensures you always use the correct `configKey`, even if keys are auto-generated |
164
+ | **Configs can be added or removed at runtime** | ✅ **Always** | Prevents mismatched lookups or stale references |
165
+
166
+ > **✔ Best Practice:**
167
+ > Always call `useWatchConfigs()` when rendering **lists of lines or accounts**, to ensure that each component receives the correct and *current* `configKey`.
168
+
169
+ ---
170
+
171
+ Example (Rendering a phone UI for *every* config dynamically):
172
+
173
+ ```tsx
174
+ const App = () => {
175
+ const configs = SipConnection.useWatchConfigs();
176
+
177
+ return (
178
+ <>
179
+ {configs.map((config) => (
180
+ <Lines key={config.key} configKey={config.key} />
181
+ ))}
182
+ </>
183
+ );
184
+ };
185
+ ```
186
+
187
+ ---
195
188
 
196
- Each SIP account supports account, media, and feature settings:
189
+ ## ⚙️ Configuration Schema
197
190
 
198
191
  ```ts
199
192
  {
193
+ key?: string; // optional → falls back to account.username
200
194
  account: {
201
195
  domain: 'your.sip.domain',
202
196
  username: 'user',
@@ -205,38 +199,41 @@ Each SIP account supports account, media, and feature settings:
205
199
  webSocketPort: '8089',
206
200
  serverPath: '/ws',
207
201
  },
208
- features: { enableVideo: true },
209
- media: {
210
- audioInputDeviceId: 'default',
211
- audioOutputDeviceId: 'default',
212
- videoInputDeviceId: 'default',
202
+ features?: { enableVideo?: boolean },
203
+ media?: {
204
+ audioInputDeviceId?: string;
205
+ audioOutputDeviceId?: string;
206
+ videoInputDeviceId?: string;
213
207
  },
214
- registration: { registerExpires: 3600 },
208
+ registration?: { registerExpires?: number },
215
209
  }
216
210
  ```
217
211
 
218
212
  ---
219
213
 
220
- ## 💡 Best Practices
214
+ ## Best Practices
221
215
 
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)`.
216
+ | Task | Recommended Approach |
217
+ | ---------------------- | --------------------------------- |
218
+ | Reference active call | Use `lineKey` when available |
219
+ | Reference account | Use `configKey` |
220
+ | Lookup by phone number | Use `{ configKey, remoteNumber }` |
221
+ | Track config keys | Use `useWatchConfigs()` |
222
+ | Watch configs state | `useWatchConfigs()` |
223
+ | Watch account state | `getAccountBy().watch()` |
224
+ | Watch session props | `useWatchSessionData()` |
228
225
 
229
226
  ---
230
227
 
231
- ## 🧑‍💻 Examples
228
+ ## 📂 Examples
232
229
 
233
230
  Check [`/example`](https://github.com/shervin-ghajar/react-sip-kit/tree/main/example) for:
234
231
 
235
- * Multi-account setups
236
- * Audio & video calls
237
- * Hold, mute, attended transfer
232
+ * Multi-account softphone
233
+ * Audio/video calls
234
+ * Hold / mute / attended transfer
238
235
  * Call recording & screen sharing
239
- * Local & remote media rendering
236
+ * Stream rendering
240
237
 
241
238
  ---
242
239
 
@@ -250,6 +247,6 @@ MIT
250
247
 
251
248
  **Shervin Ghajar**
252
249
 
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)
250
+ * GitHub: [https://github.com/shervin-ghajar](https://github.com/shervin-ghajar)
251
+ * NPM: [https://www.npmjs.com/package/react-sip-kit](https://www.npmjs.com/package/react-sip-kit)
252
+ * 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
  }): {