@voipsdk/react-native-voipcloud 1.0.5 → 1.0.7

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 CHANGED
@@ -5,6 +5,60 @@ All notable changes to `@voipsdk/react-native-voipcloud` are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.7] - 2026-08-13
9
+
10
+ Folds in the SDP bandwidth-modifier fixes that were living as app-side
11
+ `patch-package` patches (`@voipsdk+react-native-voipcloud+1.0.6.patch`), so a
12
+ fresh install behaves correctly without any patch. Root-cause mitigation for a
13
+ malformed `b=` line (e.g. `=:40` / `=:4000` / `:=4000`) that some PBX/proxy SDP
14
+ rewriters produce and that breaks SDP parsing / call negotiation.
15
+
16
+ ### Changed / Fixed
17
+
18
+ - **iOS:** disable pjmedia's `b=TIAS:<bps>` SDP bandwidth modifier by setting
19
+ `pjmedia_add_bandwidth_tias_in_sdp = PJ_FALSE` right after `libInit`. pjmedia
20
+ stamps that line onto every SDP we author (offer + answer), and some
21
+ PBX/proxy SDP rewriters mangle it (leaving `=:40` / `=:4000`) → corrupt SDP /
22
+ broken call info. We never rely on TIAS, so it is disabled at the source; only
23
+ SDP we author is affected, remote SDP is untouched.
24
+ - **Android:** `ManagedCall.onCallSdpCreated` now sanitizes both the local and
25
+ the remote SDP just before/after negotiation (`sanitizeSdp`). It keeps only
26
+ strictly RFC 4566 well-formed `<type>=<value>` lines (single `a-z` key,
27
+ non-empty value) and drops every bandwidth (`b=`) line, so a single corrupt
28
+ line can never invalidate the whole SDP. Line endings are normalized to CRLF
29
+ and the session is only rewritten when something actually changed.
30
+
31
+ ## [1.0.6] - 2026-08-10
32
+
33
+ Folds in the push-wake registration-refresh fixes that were living as an app-side
34
+ `patch-package` patch (`@voipsdk+react-native-voipcloud+1.0.5.patch`), so a fresh
35
+ install behaves correctly without any patch. Root-cause mitigation for the
36
+ multi-contact fork that yields **482 Loop Detected** and slow-or-missed calls
37
+ after a NAT-port change on a push-wake.
38
+
39
+ ### Added
40
+
41
+ - `VoipCloud.refreshRegistration(accountId)` — re-send REGISTER on the EXISTING
42
+ account (renew, same socket/binding) instead of `unregister()` + `register()`.
43
+ Reuses the live PJSIP account so the registrar updates the SAME binding — no new
44
+ contact, no fork. Prefer it over `register()` on a push-wake / periodic refresh;
45
+ rejects `VC_REFRESH` if the account was never registered (fall back to
46
+ `register()` on a cold start). Wired on iOS (`refreshRegistrationAccount:error:`)
47
+ and Android.
48
+ - Stable per-install SIP instance id (`+sip.instance`, RFC 5626 form) on the
49
+ Contact of each account, so a one-contact-per-AOR registrar (Kamailio
50
+ `max_contacts=1` / `matching_mode=instance`, Asterisk PJSIP `remove_existing=yes`)
51
+ replaces the device's previous binding instead of appending. Uses
52
+ `identifierForVendor` (iOS) / `ANDROID_ID` (Android); harmless if the server
53
+ ignores it.
54
+
55
+ ### Changed / Fixed
56
+
57
+ - **iOS:** `natConfig.contactUseSrcPort = 1` — keep the learned NAT source **port**
58
+ (not just the IP) in the rewritten Contact, matching Android. Fixes stale-port
59
+ Contacts after an iOS suspend/rebind that routed inbound INVITEs to a dead port
60
+ and prevented re-REGISTER from matching the old binding.
61
+
8
62
  ## [1.0.3] - 2026-07-17
9
63
 
10
64
  Folds in the NAT / media / call-routing fixes that were living as app-side
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @voipsdk/react-native-voipcloud
2
2
 
3
3
  > Public VoIP SDK for React Native — Android **and** iOS (RN 0.70+).
4
- > Package: `@voipsdk/react-native-voipcloud@1.0.5`
4
+ > Package: `@voipsdk/react-native-voipcloud@1.0.7`
5
5
 
6
6
  Android and iOS expose the **same** JS API, events and `VC_*` error codes.
7
7
 
@@ -146,6 +146,7 @@ export function useVoipEvents() {
146
146
  | `shutdown()` | Hang up all, destroy endpoint |
147
147
  | `register(account)` | Register SIP account → `accountId` |
148
148
  | `unregister(accountId)` | Unregister account |
149
+ | `refreshRegistration(accountId)` | Re-REGISTER an existing account (renew, same binding) — push-wake refresh that avoids contact churn / 482 |
149
150
  | `makeCall(accountId, uri)` | Outbound call → `callId` |
150
151
  | `answer(callId)` | Answer incoming call |
151
152
  | `hangup(callId, mode?)` | End one call (`mode`: `'normal'` \| `'busy'` \| `'cancel'`) |
@@ -276,6 +276,25 @@ public class VoipCloudModule extends ReactContextBaseJavaModule {
276
276
  AuthCredInfo cred = new AuthCredInfo("digest", "*", username, 0, password);
277
277
  acfg.getSipConfig().getAuthCreds().add(cred);
278
278
 
279
+ // Stable per-install SIP instance id (+sip.instance) so the registrar
280
+ // can REPLACE this device's previous binding on re-REGISTER instead of
281
+ // appending a new contact — root-cause mitigation for the multi-contact
282
+ // fork that yields 482 Loop Detected (needs a one-contact-per-AOR server
283
+ // policy; harmless if the server ignores it). ANDROID_ID is stable per
284
+ // app-signing-key per device and resets on reinstall — the lifetime we
285
+ // want for a device-binding id. Mirrors the iOS engine
286
+ // (identifierForVendor). Wrapped so a lookup failure never breaks
287
+ // registration. The value is matched opaquely by the registrar.
288
+ try {
289
+ String androidId = android.provider.Settings.Secure.getString(
290
+ ctx.getContentResolver(),
291
+ android.provider.Settings.Secure.ANDROID_ID);
292
+ if (androidId != null && !androidId.isEmpty()) {
293
+ acfg.getSipConfig().setContactParams(
294
+ ";+sip.instance=\"<urn:uuid:" + androidId + ">\"");
295
+ }
296
+ } catch (Throwable ignored) {}
297
+
279
298
  ManagedAccount acc = new ManagedAccount(ctx, calls, endpoint);
280
299
  acc.create(acfg);
281
300
  int id = acc.accountId();
@@ -306,6 +325,29 @@ public class VoipCloudModule extends ReactContextBaseJavaModule {
306
325
  }
307
326
  }
308
327
 
328
+ // Re-REGISTER on the EXISTING account (renew) instead of unregister()+register().
329
+ // On a push-wake the account/transport are still alive, so this re-REGISTERs
330
+ // from the SAME socket and the registrar updates the SAME binding — no new
331
+ // contact, no fork (the churn behind 482 Loop Detected / slow-or-missed calls).
332
+ // Prefer this over unregister+register whenever the account already exists
333
+ // (see VoipCloudSipManager warm-refresh path).
334
+ @ReactMethod
335
+ public void refreshRegistration(int accountId, Promise promise) {
336
+ if (!BridgeGuard.ensureNative(promise)) return;
337
+ try {
338
+ ManagedAccount acc = accounts.get(accountId);
339
+ if (acc == null) {
340
+ promise.reject("VC_REFRESH",
341
+ "Unknown accountId " + accountId + ". Call register() first.");
342
+ return;
343
+ }
344
+ acc.setRegistration(true);
345
+ promise.resolve(null);
346
+ } catch (Throwable t) {
347
+ BridgeGuard.reject(promise, "VC_REFRESH", "Failed to refresh registration", t);
348
+ }
349
+ }
350
+
309
351
  @ReactMethod
310
352
  public void makeCall(int accountId, String uri, Promise promise) {
311
353
  if (!BridgeGuard.ensureNative(promise)) return;
@@ -12,9 +12,11 @@ import com.voipcloud.sdk.CallInfo;
12
12
  import com.voipcloud.sdk.CallMediaInfo;
13
13
  import com.voipcloud.sdk.Endpoint;
14
14
  import com.voipcloud.sdk.OnCallMediaStateParam;
15
+ import com.voipcloud.sdk.OnCallSdpCreatedParam;
15
16
  import com.voipcloud.sdk.OnCallStateParam;
16
17
  import com.voipcloud.sdk.OnCallTransferStatusParam;
17
18
  import com.voipcloud.sdk.OnDtmfDigitParam;
19
+ import com.voipcloud.sdk.SdpSession;
18
20
  import com.voipcloud.sdk.pjmedia_type;
19
21
  import com.voipcloud.sdk.pjsip_inv_state;
20
22
  import com.voipcloud.sdk.pjsua_call_media_status;
@@ -189,6 +191,72 @@ public final class ManagedCall extends Call {
189
191
  emitMediaState();
190
192
  }
191
193
 
194
+ /**
195
+ * Fired after PJSIP builds the local SDP (our offer for outgoing calls, our
196
+ * answer for incoming ones, and re-offer/answer on hold/unhold) but BEFORE
197
+ * it is sent. We sanitize both the SDP we send and PJSIP's copy of the
198
+ * remote SDP so a malformed line or a bandwidth modifier can never break
199
+ * negotiation — see {@link #sanitizeSdp}.
200
+ */
201
+ @Override
202
+ public void onCallSdpCreated(OnCallSdpCreatedParam prm) {
203
+ try {
204
+ sanitizeSdp(prm.getSdp());
205
+ sanitizeSdp(prm.getRemSdp());
206
+ } catch (Throwable ignored) {}
207
+ }
208
+
209
+ /**
210
+ * Rewrite an SdpSession in place, keeping ONLY strictly well-formed lines
211
+ * and dropping every bandwidth ("b=") line.
212
+ *
213
+ * Per RFC 4566 every SDP line is {@code <type>=<value>} where {@code <type>}
214
+ * is exactly one letter and {@code <value>} is non-empty. We enforce that
215
+ * grammar and treat anything else as corruption to be removed — not just the
216
+ * one observed artifact ({@code :=4000}) but ANY bad key or value:
217
+ * <ul>
218
+ * <li>key is not a single {@code a-z} letter — {@code :=4}, {@code S=4},
219
+ * {@code =64000}, {@code 3=x}, {@code " b=…"} (leading space)…</li>
220
+ * <li>second char is not {@code '='} — {@code ba=…}, {@code b:AS…}…</li>
221
+ * <li>empty value — {@code a=} with nothing after {@code '='}</li>
222
+ * </ul>
223
+ * Dropping a bad line (rather than trusting it) means a single corrupt line
224
+ * can never invalidate the whole SDP, and the value it carried is irrelevant.
225
+ * Line endings are normalized to CRLF. No-op when the SDP is absent; the
226
+ * session is only rewritten if something actually changed.
227
+ */
228
+ private static void sanitizeSdp(SdpSession session) {
229
+ if (session == null) return;
230
+ String sdp;
231
+ try { sdp = session.getWholeSdp(); } catch (Throwable e) { return; }
232
+ if (sdp == null || sdp.isEmpty()) return;
233
+
234
+ // Split on any newline style (CRLF / CR / LF) so a stray ending cannot
235
+ // smuggle a corrupt token through as part of another line.
236
+ String[] lines = sdp.split("\r\n|\r|\n");
237
+ StringBuilder out = new StringBuilder(sdp.length());
238
+ boolean changed = false;
239
+ for (String line : lines) {
240
+ int len = line.length();
241
+ if (len == 0) { continue; }
242
+ char key = line.charAt(0);
243
+ boolean wellFormed =
244
+ len >= 3 // key + '=' + at least one value char
245
+ && line.charAt(1) == '=' // "<key>=" separator
246
+ && key >= 'a' && key <= 'z'; // key is a single a-z letter only
247
+ if (!wellFormed || key == 'b') { // corrupt/invalid line, or bandwidth
248
+ changed = true;
249
+ continue;
250
+ }
251
+ out.append(line).append("\r\n");
252
+ }
253
+ // Only push back a non-empty result; a real SDP always keeps v=/o=/… so
254
+ // out is never empty in practice, but guard against wiping the session.
255
+ if (changed && out.length() > 0) {
256
+ try { session.setWholeSdp(out.toString()); } catch (Throwable ignored) {}
257
+ }
258
+ }
259
+
192
260
  /**
193
261
  * Multi-stage progress notifications for a REFER (call transfer)
194
262
  * we initiated. Fires for each NOTIFY received from the remote
package/ios/VoipCloud.mm CHANGED
@@ -98,6 +98,17 @@ RCT_EXPORT_METHOD(unregister:(NSInteger)accountId
98
98
  else reject(@"VC_UNREGISTER", err.localizedDescription ?: @"Failed to unregister account", err);
99
99
  }
100
100
 
101
+ // Re-REGISTER on the existing account (renew, same socket) — the push-wake refresh
102
+ // that avoids the unregister+recreate contact churn behind 482 / slow calls.
103
+ RCT_EXPORT_METHOD(refreshRegistration:(NSInteger)accountId
104
+ resolver:(RCTPromiseResolveBlock)resolve
105
+ rejecter:(RCTPromiseRejectBlock)reject)
106
+ {
107
+ NSError *err = nil;
108
+ if ([VoipCloudEngine.shared refreshRegistrationAccount:(int)accountId error:&err]) resolve(nil);
109
+ else reject(@"VC_REFRESH", err.localizedDescription ?: @"Failed to refresh registration", err);
110
+ }
111
+
101
112
  #pragma mark - Calls
102
113
 
103
114
  RCT_EXPORT_METHOD(makeCall:(NSInteger)accountId
@@ -42,6 +42,10 @@ NS_ASSUME_NONNULL_BEGIN
42
42
  /// Returns the accountId, or -1 on failure (with *error set).
43
43
  - (int)registerAccountWithConfig:(NSDictionary *)cfg error:(NSError **)error;
44
44
  - (BOOL)unregisterAccount:(int)accountId error:(NSError **)error;
45
+ /// Re-send REGISTER on the EXISTING account (renew, same socket/Contact) instead
46
+ /// of unregister+recreate — the push-wake refresh that avoids contact churn/482.
47
+ /// NO (with *error) if the account was never registered.
48
+ - (BOOL)refreshRegistrationAccount:(int)accountId error:(NSError **)error;
45
49
 
46
50
  /// Place a call from `accountId` to `uri` (full SIP URI). Returns callId, or -1.
47
51
  - (int)makeCallFromAccount:(int)accountId toUri:(NSString *)uri error:(NSError **)error;
@@ -4,6 +4,7 @@
4
4
  //
5
5
  #import "VoipCloudEngine.h"
6
6
  #import <AVFoundation/AVFoundation.h>
7
+ #import <UIKit/UIKit.h> // UIDevice.identifierForVendor — stable per-install SIP instance id
7
8
  #include <pjsua2.hpp>
8
9
  #include <pjsua-lib/pjsua.h>
9
10
  #include <string>
@@ -13,6 +14,15 @@
13
14
 
14
15
  using namespace pj;
15
16
 
17
+ // pjmedia run-time flag (defined in pjmedia endpoint.c, C linkage). Default 1 →
18
+ // pjmedia stamps a "b=TIAS:<bps>" bandwidth modifier onto every SDP it generates
19
+ // (our outgoing offer + our answer). Some PBX/proxy SDP rewriters mangle that line
20
+ // (e.g. leaving "=:40"/"=:4000") → corrupt SDP / broken call info. We never rely on
21
+ // TIAS, so disable at the source. Only touches SDP *we* author; remote SDP is
22
+ // untouched (strip those at the PBX). Declared here because config.h only mentions
23
+ // it in a doc comment, not as a real extern.
24
+ extern "C" pj_bool_t pjmedia_add_bandwidth_tias_in_sdp;
25
+
16
26
  static NSString *const kVCErrorDomain = @"VoipCloudEngine";
17
27
 
18
28
  static NSError *VCMakeError(NSInteger code, NSString *msg) {
@@ -322,6 +332,11 @@ void VCCall::onDtmfDigit(OnDtmfDigitParam &prm) {
322
332
 
323
333
  _ep->libInit(epCfg);
324
334
 
335
+ // Never emit the SDP "b=TIAS:" bandwidth modifier — it triggers broken
336
+ // SDP rewrites downstream (=:40 / =:4000) and we don't use it. Safe to set
337
+ // once the pjmedia endpoint exists (after libInit); read at SDP-gen time.
338
+ pjmedia_add_bandwidth_tias_in_sdp = PJ_FALSE;
339
+
325
340
  // Ephemeral ports (0) avoid "Address already in use" for SIP clients.
326
341
  // Keep each transport id so registerAccountWithConfig can pin the
327
342
  // account to the matching one (0 is a valid id → init to -1).
@@ -419,10 +434,40 @@ void VCCall::onDtmfDigit(OnDtmfDigitParam &prm) {
419
434
  acfg.natConfig.contactRewriteUse = 1;
420
435
  acfg.natConfig.viaRewriteUse = 1;
421
436
  acfg.natConfig.udpKaIntervalSec = 20;
437
+ // Keep the learned NAT source PORT (not just the source IP) in the
438
+ // rewritten Contact — PARITY WITH ANDROID (setContactUseSrcPort(1)),
439
+ // previously missing on iOS. After iOS suspends the app the OS rebinds the
440
+ // NAT pinhole to a NEW port; the next REGISTER must advertise the EXACT
441
+ // ip:port it now sends from. Without this the Contact can carry a stale
442
+ // port, so (a) inbound INVITEs route to the dead port and (b) an
443
+ // unregister/re-REGISTER from the new port cannot match the old binding at
444
+ // the registrar → the AOR accumulates contacts → the PBX forks the INVITE
445
+ // to all of them → 482 Loop Detected + losing branches CANCELled.
446
+ acfg.natConfig.contactUseSrcPort = 1;
422
447
  acfg.regConfig.timeoutSec = 120;
423
448
  acfg.regConfig.firstRetryIntervalSec = 5;
424
449
  acfg.regConfig.retryIntervalSec = 30;
425
450
 
451
+ // Stable per-install SIP instance id (+sip.instance, RFC 5626 form). The
452
+ // registrar can then REPLACE this device's previous binding on every
453
+ // re-REGISTER instead of appending a new contact. Combined with a
454
+ // one-contact-per-AOR server policy (Kamailio max_contacts=1 /
455
+ // matching_mode=instance, or Asterisk PJSIP remove_existing=yes) this is
456
+ // the root-cause fix for the multi-contact fork that yields 482 Loop
457
+ // Detected after a NAT-port change on a push-wake. It is HARMLESS when the
458
+ // server ignores it (just an extra Contact param). identifierForVendor is
459
+ // stable across launches/suspends for this vendor's apps and only resets on
460
+ // reinstall — exactly the lifetime wanted for a device-binding id. We set
461
+ // it via contactParams (Contact header suffix) rather than enabling full
462
+ // outbound (sipOutboundUse), which would require server Path support.
463
+ NSString *vendorId = [[UIDevice currentDevice] identifierForVendor].UUIDString;
464
+ if (vendorId.length) {
465
+ std::string instance = ";+sip.instance=\"<urn:uuid:";
466
+ instance += std::string(vendorId.UTF8String);
467
+ instance += ">\"";
468
+ acfg.sipConfig.contactParams = instance;
469
+ }
470
+
426
471
  // STUN for MEDIA only: without it the SDP offer carries the LAN IP
427
472
  // (c=192.168.x) and the far end sends RTP to an unroutable address →
428
473
  // no 2-way audio. Falls back to the host addr if STUN is unreachable.
@@ -486,6 +531,35 @@ void VCCall::onDtmfDigit(OnDtmfDigitParam &prm) {
486
531
  return YES;
487
532
  }
488
533
 
534
+ // Re-send REGISTER on the EXISTING account instead of unregister()+register().
535
+ //
536
+ // On an iOS VoIP push-wake the app was only SUSPENDED (not killed): the PJSUA
537
+ // account and its pinned UDP transport/socket are still alive, so a plain refresh
538
+ // re-REGISTERs from the SAME NAT source port and the registrar updates the SAME
539
+ // binding. Delete+recreate (the old path) instead built a brand-new account on a
540
+ // new ephemeral port, so the registrar saw a NEW contact while the stale one
541
+ // lingered — the multi-contact churn behind 482 Loop Detected / slow-or-missed
542
+ // calls. Prefer this whenever the account already exists (see VoipCloudSipManager
543
+ // warm-refresh path); fall back to register() only on a cold start (no account).
544
+ //
545
+ // setRegistration(true) forces PJSIP to send REGISTER now (renew) even before the
546
+ // timer elapses; autoreg stays on so periodic refresh continues normally.
547
+ - (BOOL)refreshRegistrationAccount:(int)accountId error:(NSError **)error {
548
+ auto it = _accounts.find(accountId);
549
+ if (it == _accounts.end()) {
550
+ if (error) *error = VCMakeError(-1, [NSString stringWithFormat:
551
+ @"Unknown accountId %d. Call register() first.", accountId]);
552
+ return NO;
553
+ }
554
+ try {
555
+ it->second->setRegistration(true);
556
+ return YES;
557
+ } catch (Error &err) {
558
+ if (error) *error = VCMakeError(err.status, VCStr(err.info()));
559
+ return NO;
560
+ }
561
+ }
562
+
489
563
  #pragma mark Calls
490
564
 
491
565
  - (int)makeCallFromAccount:(int)accountId toUri:(NSString *)uri error:(NSError **)error {
@@ -39,6 +39,17 @@ const VoipCloud = exports.VoipCloud = {
39
39
  unregister(accountId) {
40
40
  return callNative(() => _nativeModule.NativeVoipCloud.unregister(accountId));
41
41
  },
42
+ /**
43
+ * Re-send REGISTER on an EXISTING account (renew) without unregister+recreate.
44
+ * Reuses the same PJSIP account/socket, so the registrar updates the SAME
45
+ * binding — no new contact. Prefer this over `register()` on a push-wake or
46
+ * periodic refresh (avoids the multi-contact churn behind 482 Loop Detected /
47
+ * slow-or-missed calls). Rejects with `VC_REFRESH` if `accountId` was never
48
+ * registered — the caller should fall back to `register()` on a cold start.
49
+ */
50
+ refreshRegistration(accountId) {
51
+ return callNative(() => _nativeModule.NativeVoipCloud.refreshRegistration(accountId));
52
+ },
42
53
  makeCall(accountId, destinationUri) {
43
54
  return callNative(() => _nativeModule.NativeVoipCloud.makeCall(accountId, destinationUri));
44
55
  },
@@ -1 +1 @@
1
- {"version":3,"names":["_nativeModule","require","_events","rejectNotLinked","Promise","reject","Object","assign","Error","code","callNative","fn","NativeVoipCloud","VoipCloud","exports","init","cfg","shutdown","register","account","unregister","accountId","makeCall","destinationUri","answer","callId","hangup","mode","hold","unhold","mute","on","sendDtmf","digits","transferCall","hangupAllCalls","reconnectCallAudio"],"sourceRoot":"../../src","sources":["VoipCloud.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_nativeModule","require","_events","rejectNotLinked","Promise","reject","Object","assign","Error","code","callNative","fn","NativeVoipCloud","VoipCloud","exports","init","cfg","shutdown","register","account","unregister","accountId","refreshRegistration","makeCall","destinationUri","answer","callId","hangup","mode","hold","unhold","mute","on","sendDtmf","digits","transferCall","hangupAllCalls","reconnectCallAudio"],"sourceRoot":"../../src","sources":["VoipCloud.ts"],"mappings":"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","ignoreList":[]}
@@ -34,6 +34,17 @@ export const VoipCloud = {
34
34
  unregister(accountId) {
35
35
  return callNative(() => NativeVoipCloud.unregister(accountId));
36
36
  },
37
+ /**
38
+ * Re-send REGISTER on an EXISTING account (renew) without unregister+recreate.
39
+ * Reuses the same PJSIP account/socket, so the registrar updates the SAME
40
+ * binding — no new contact. Prefer this over `register()` on a push-wake or
41
+ * periodic refresh (avoids the multi-contact churn behind 482 Loop Detected /
42
+ * slow-or-missed calls). Rejects with `VC_REFRESH` if `accountId` was never
43
+ * registered — the caller should fall back to `register()` on a cold start.
44
+ */
45
+ refreshRegistration(accountId) {
46
+ return callNative(() => NativeVoipCloud.refreshRegistration(accountId));
47
+ },
37
48
  makeCall(accountId, destinationUri) {
38
49
  return callNative(() => NativeVoipCloud.makeCall(accountId, destinationUri));
39
50
  },
@@ -1 +1 @@
1
- {"version":3,"names":["NativeVoipCloud","on","rejectNotLinked","Promise","reject","Object","assign","Error","code","callNative","fn","VoipCloud","init","cfg","shutdown","register","account","unregister","accountId","makeCall","destinationUri","answer","callId","hangup","mode","hold","unhold","mute","sendDtmf","digits","transferCall","hangupAllCalls","reconnectCallAudio"],"sourceRoot":"../../src","sources":["VoipCloud.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["NativeVoipCloud","on","rejectNotLinked","Promise","reject","Object","assign","Error","code","callNative","fn","VoipCloud","init","cfg","shutdown","register","account","unregister","accountId","refreshRegistration","makeCall","destinationUri","answer","callId","hangup","mode","hold","unhold","mute","sendDtmf","digits","transferCall","hangupAllCalls","reconnectCallAudio"],"sourceRoot":"../../src","sources":["VoipCloud.ts"],"mappings":"AACA,SAASA,eAAe,QAAQ,gBAAgB;AAChD,SAASC,EAAE,QAAQ,UAAU;;AAE7B;AACA,SAASC,eAAeA,CAAA,EAAmB;EACvC,OAAOC,OAAO,CAACC,MAAM,CACjBC,MAAM,CAACC,MAAM,CACT,IAAIC,KAAK,CACL,+DAA+D,GAC/D,+CAA+C,GAC/C,gFACJ,CAAC,EACD;IAAEC,IAAI,EAAE;EAAgB,CAC5B,CACJ,CAAC;AACL;AAEA,SAASC,UAAUA,CAAIC,EAAoB,EAAc;EACrD,IAAI,CAACV,eAAe,EAAE,OAAOE,eAAe,CAAC,CAAC;EAC9C,OAAOQ,EAAE,CAAC,CAAC;AACf;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,SAAS,GAAG;EACrBC,IAAIA,CAACC,GAAe,GAAG,CAAC,CAAC,EAAiB;IACtC,OAAOJ,UAAU,CAAC,MAAMT,eAAe,CAACY,IAAI,CAACC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;EAC5D,CAAC;EAEDC,QAAQA,CAAA,EAAkB;IACtB,OAAOL,UAAU,CAAC,MAAMT,eAAe,CAACc,QAAQ,CAAC,CAAC,CAAC;EACvD,CAAC;EAEDC,QAAQA,CAACC,OAAsB,EAAmB;IAC9C,OAAOP,UAAU,CAAC,MAAMT,eAAe,CAACe,QAAQ,CAACC,OAAO,CAAC,CAAC;EAC9D,CAAC;EAEDC,UAAUA,CAACC,SAAiB,EAAiB;IACzC,OAAOT,UAAU,CAAC,MAAMT,eAAe,CAACiB,UAAU,CAACC,SAAS,CAAC,CAAC;EAClE,CAAC;EAED;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;EACIC,mBAAmBA,CAACD,SAAiB,EAAiB;IAClD,OAAOT,UAAU,CAAC,MAAMT,eAAe,CAACmB,mBAAmB,CAACD,SAAS,CAAC,CAAC;EAC3E,CAAC;EAEDE,QAAQA,CAACF,SAAiB,EAAEG,cAAsB,EAAmB;IACjE,OAAOZ,UAAU,CAAC,MAAMT,eAAe,CAACoB,QAAQ,CAACF,SAAS,EAAEG,cAAc,CAAC,CAAC;EAChF,CAAC;EAEDC,MAAMA,CAACC,MAAc,EAAiB;IAClC,OAAOd,UAAU,CAAC,MAAMT,eAAe,CAACsB,MAAM,CAACC,MAAM,CAAC,CAAC;EAC3D,CAAC;EAEDC,MAAMA,CAACD,MAAc,EAAEE,IAAkC,GAAG,QAAQ,EAAiB;IACjF,OAAOhB,UAAU,CAAC,MAAMT,eAAe,CAACwB,MAAM,CAACD,MAAM,EAAEE,IAAI,CAAC,CAAC;EACjE,CAAC;EAEDC,IAAIA,CAACH,MAAc,EAAiB;IAChC,OAAOd,UAAU,CAAC,MAAMT,eAAe,CAAC0B,IAAI,CAACH,MAAM,CAAC,CAAC;EACzD,CAAC;EAEDI,MAAMA,CAACJ,MAAc,EAAiB;IAClC,OAAOd,UAAU,CAAC,MAAMT,eAAe,CAAC2B,MAAM,CAACJ,MAAM,CAAC,CAAC;EAC3D,CAAC;EAEDK,IAAIA,CAACL,MAAc,EAAEtB,EAAW,EAAiB;IAC7C,OAAOQ,UAAU,CAAC,MAAMT,eAAe,CAAC4B,IAAI,CAACL,MAAM,EAAEtB,EAAE,CAAC,CAAC;EAC7D,CAAC;EAED4B,QAAQA,CAACN,MAAc,EAAEO,MAAc,EAAiB;IACpD,OAAOrB,UAAU,CAAC,MAAMT,eAAe,CAAC6B,QAAQ,CAACN,MAAM,EAAEO,MAAM,CAAC,CAAC;EACrE,CAAC;EAEDC,YAAYA,CAACR,MAAc,EAAEF,cAAsB,EAAiB;IAChE,OAAOZ,UAAU,CAAC,MAAMT,eAAe,CAAC+B,YAAY,CAACR,MAAM,EAAEF,cAAc,CAAC,CAAC;EACjF,CAAC;EAEDW,cAAcA,CAAA,EAAkB;IAC5B,OAAOvB,UAAU,CAAC,MAAMT,eAAe,CAACgC,cAAc,CAAC,CAAC,CAAC;EAC7D,CAAC;EAEDC,kBAAkBA,CAACV,MAAc,EAAiB;IAC9C,OAAOd,UAAU,CAAC,MAAMT,eAAe,CAACiC,kBAAkB,CAACV,MAAM,CAAC,CAAC;EACvE,CAAC;EAEDtB;AACJ,CAAC","ignoreList":[]}
@@ -14,6 +14,15 @@ export declare const VoipCloud: {
14
14
  shutdown(): Promise<void>;
15
15
  register(account: AccountConfig): Promise<number>;
16
16
  unregister(accountId: number): Promise<void>;
17
+ /**
18
+ * Re-send REGISTER on an EXISTING account (renew) without unregister+recreate.
19
+ * Reuses the same PJSIP account/socket, so the registrar updates the SAME
20
+ * binding — no new contact. Prefer this over `register()` on a push-wake or
21
+ * periodic refresh (avoids the multi-contact churn behind 482 Loop Detected /
22
+ * slow-or-missed calls). Rejects with `VC_REFRESH` if `accountId` was never
23
+ * registered — the caller should fall back to `register()` on a cold start.
24
+ */
25
+ refreshRegistration(accountId: number): Promise<void>;
17
26
  makeCall(accountId: number, destinationUri: string): Promise<number>;
18
27
  answer(callId: number): Promise<void>;
19
28
  hangup(callId: number, mode?: "busy" | "cancel" | "normal"): Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@voipsdk/react-native-voipcloud",
3
- "version": "1.0.5",
4
- "description": "Public React Native VoIP SDK backed by PJSIP (VoipCloud).",
3
+ "version": "1.0.7",
4
+ "description": "Public React Native VoIP SDK (VoipCloud).",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
7
7
  "react-native": "src/index.ts",
package/src/VoipCloud.ts CHANGED
@@ -47,6 +47,18 @@ export const VoipCloud = {
47
47
  return callNative(() => NativeVoipCloud.unregister(accountId));
48
48
  },
49
49
 
50
+ /**
51
+ * Re-send REGISTER on an EXISTING account (renew) without unregister+recreate.
52
+ * Reuses the same PJSIP account/socket, so the registrar updates the SAME
53
+ * binding — no new contact. Prefer this over `register()` on a push-wake or
54
+ * periodic refresh (avoids the multi-contact churn behind 482 Loop Detected /
55
+ * slow-or-missed calls). Rejects with `VC_REFRESH` if `accountId` was never
56
+ * registered — the caller should fall back to `register()` on a cold start.
57
+ */
58
+ refreshRegistration(accountId: number): Promise<void> {
59
+ return callNative(() => NativeVoipCloud.refreshRegistration(accountId));
60
+ },
61
+
50
62
  makeCall(accountId: number, destinationUri: string): Promise<number> {
51
63
  return callNative(() => NativeVoipCloud.makeCall(accountId, destinationUri));
52
64
  },