From dfead3f6446b5b36b2cc1ff0dae14522a3fad993 Mon Sep 17 00:00:00 2001 From: Takekatsu Hiramura Date: Fri, 9 Jun 2023 17:04:09 +0900 Subject: [PATCH 01/18] add status_code to progress event (#374) Co-authored-by: Takekatsu Hiramura --- lib/src/event_manager/call_events.dart | 4 +++- lib/src/rtc_session.dart | 12 +++++++++--- lib/src/sip_ua_helper.dart | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/src/event_manager/call_events.dart b/lib/src/event_manager/call_events.dart index 632b6351..0270a8cc 100644 --- a/lib/src/event_manager/call_events.dart +++ b/lib/src/event_manager/call_events.dart @@ -31,10 +31,12 @@ class EventCallEnded extends CallEvent { } class EventCallProgress extends CallEvent { - EventCallProgress({RTCSession? session, this.originator, this.response}) + EventCallProgress( + {RTCSession? session, this.originator, this.response, this.cause}) : super(session); String? originator; dynamic response; + ErrorCause? cause; } class EventCallConfirmed extends CallEvent { diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 57ef8174..cef21ac5 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -2361,7 +2361,7 @@ class RTCSession extends EventManager implements Owner { } _status = C.STATUS_1XX_RECEIVED; - _progress('remote', response); + _progress('remote', response, int.parse(status_code)); if (response.body == null || response.body!.isEmpty) { return; @@ -2916,11 +2916,17 @@ class RTCSession extends EventManager implements Owner { emit(EventCallConnecting(session: this, request: request)); } - void _progress(String originator, dynamic response) { + void _progress(String originator, dynamic response, [int? status_code]) { logger.d('session progress'); logger.d('emit "progress"'); + + ErrorCause errorCause = ErrorCause(status_code: status_code); + emit(EventCallProgress( - session: this, originator: originator, response: response)); + session: this, + originator: originator, + response: response, + cause: errorCause)); } void _accepted(String originator, [dynamic message]) { diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index b167cfa9..0de7c63d 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -228,8 +228,10 @@ class SIPUAHelper extends EventManager { }); handlers.on(EventCallProgress(), (EventCallProgress event) { logger.d('call is in progress'); - _notifyCallStateListeners(event, - CallState(CallStateEnum.PROGRESS, originator: event.originator)); + _notifyCallStateListeners( + event, + CallState(CallStateEnum.PROGRESS, + originator: event.originator, cause: event.cause)); }); handlers.on(EventCallFailed(), (EventCallFailed event) { logger.d('call failed with cause: ${event.cause}'); From 43a4fe869c4bc73050e3858cacc691f448fc69a7 Mon Sep 17 00:00:00 2001 From: ftsef <131373548+ftsef@users.noreply.github.com> Date: Sun, 25 Jun 2023 09:30:16 +0200 Subject: [PATCH 02/18] expose rtc_session to/from-tag (#378) Co-authored-by: Fabian Fest --- lib/src/rtc_session.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index cef21ac5..95529750 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -88,7 +88,9 @@ class RTCSession extends EventManager implements Owner { final Map _earlyDialogs = {}; String? _contact; String? _from_tag; + String? get from_tag => _from_tag; String? _to_tag; + String? get to_tag => _to_tag; // The RTCPeerConnection instance (public attribute). RTCPeerConnection? _connection; From f8aa84aa7900ec390f125176a34a096a33fbb43d Mon Sep 17 00:00:00 2001 From: Matthias Schicker Date: Sun, 25 Jun 2023 09:30:53 +0200 Subject: [PATCH 03/18] =?UTF-8?q?#376:=20Added=20`=5Fsettings.session=5Fti?= =?UTF-8?q?mers=5Frefresh=5Fmethod`=20to=20UaSettings=20t=E2=80=A6=20(#377?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #376: Added `_settings.session_timers_refresh_method` to UaSettings to expose this feature for users of SipUaHelper * Reformatted sip_ua_helper to get build pipeline to run --------- Co-authored-by: Matthias Schicker --- lib/src/sip_ua_helper.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index 0de7c63d..aaf6e56f 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -139,6 +139,8 @@ class SIPUAHelper extends EventManager { _settings.dtmf_mode = uaSettings.dtmfMode; _settings.session_timers = uaSettings.sessionTimers; _settings.ice_gathering_timeout = uaSettings.iceGatheringTimeout; + _settings.session_timers_refresh_method = + uaSettings.sessionTimersRefreshMethod; try { _ua = UA(_settings); @@ -728,4 +730,9 @@ class UaSettings { // 'credential': 'change_to_real_secret' // }, ]; + + /// Controls which kind of messages are to be sent to keep a SIP session + /// alive. + /// Defaults to "UPDATE" + DartSIP_C.SipMethod sessionTimersRefreshMethod = DartSIP_C.SipMethod.UPDATE; } From a183f1babd74d522b5441a48622d06b2beea9510 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 22 Aug 2023 20:15:45 -0500 Subject: [PATCH 04/18] Update dart.yml --- .github/workflows/dart.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 57c33126..ffb5fb69 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -22,7 +22,6 @@ jobs: java-version: '12.x' - uses: subosito/flutter-action@v1 with: - flutter-version: '3.7.3' channel: 'stable' - run: flutter packages get - run: dart format lib/ test/ --set-exit-if-changed From f1f8bb82821135783e96e589551f51bad1ea09e6 Mon Sep 17 00:00:00 2001 From: Victor Uvarov <35702719+VictorUvarov@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:18:33 -0700 Subject: [PATCH 05/18] expose instance_id (#395) --- lib/src/sip_ua_helper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index aaf6e56f..dc0994be 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -141,6 +141,7 @@ class SIPUAHelper extends EventManager { _settings.ice_gathering_timeout = uaSettings.iceGatheringTimeout; _settings.session_timers_refresh_method = uaSettings.sessionTimersRefreshMethod; + _settings.instance_id = uaSettings.instanceId; try { _ua = UA(_settings); @@ -709,6 +710,7 @@ class UaSettings { String? password; String? ha1; String? displayName; + String? instanceId; /// DTMF mode, in band (rfc2833) or out of band (sip info) DtmfMode dtmfMode = DtmfMode.INFO; From 6a87d71e7f8bc778d88c42daa9707824337f9ae2 Mon Sep 17 00:00:00 2001 From: Victor Uvarov <35702719+VictorUvarov@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:20:22 -0700 Subject: [PATCH 06/18] Example app cleanup (#394) * Example app cleanup - mirror local video when front facing camera - run dart format - update dependencies * try a newer version of stable flutter --------- Co-authored-by: CloudWebRTC --- example/lib/main.dart | 13 +- example/lib/src/about.dart | 28 +-- example/lib/src/callscreen.dart | 152 +++++++----- example/lib/src/dialpad.dart | 276 +++++++++++---------- example/lib/src/register.dart | 248 +++++++----------- example/lib/src/widgets/action_button.dart | 2 +- example/pubspec.yaml | 11 +- 7 files changed, 350 insertions(+), 380 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index f21e9260..e1c9b8e5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:sip_ua/sip_ua.dart'; - import 'src/about.dart'; import 'src/callscreen.dart'; import 'src/dialpad.dart'; @@ -57,6 +56,18 @@ class MyApp extends StatelessWidget { theme: ThemeData( primarySwatch: Colors.blue, fontFamily: 'Roboto', + inputDecorationTheme: InputDecorationTheme( + hintStyle: TextStyle(color: Colors.grey), + contentPadding: EdgeInsets.all(10.0), + border: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.black12)), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(16), + textStyle: TextStyle(fontSize: 18), + ), + ), ), initialRoute: '/', onGenerateRoute: _onGenerateRoute, diff --git a/example/lib/src/about.dart b/example/lib/src/about.dart index 7109cdf8..f4eacedc 100644 --- a/example/lib/src/about.dart +++ b/example/lib/src/about.dart @@ -4,21 +4,21 @@ class AboutWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("About"), - ), - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(18.0), - child: Center( - child: Column( - children: [ - Text( - 'GitHub:\nhttps://github.com/cloudwebrtc/dart-sip-ua.git') - ], - ), + appBar: AppBar( + title: Text("About"), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(18.0), + child: Center( + child: Column( + children: [ + Text('GitHub:\nhttps://github.com/cloudwebrtc/dart-sip-ua.git'), + ], ), ), - )); + ), + ), + ); } } diff --git a/example/lib/src/callscreen.dart b/example/lib/src/callscreen.dart index 0bfa394b..1777efe3 100644 --- a/example/lib/src/callscreen.dart +++ b/example/lib/src/callscreen.dart @@ -10,9 +10,11 @@ import 'widgets/action_button.dart'; class CallScreenWidget extends StatefulWidget { final SIPUAHelper? _helper; final Call? _call; + CallScreenWidget(this._helper, this._call, {Key? key}) : super(key: key); + @override - _MyCallScreenWidget createState() => _MyCallScreenWidget(); + State createState() => _MyCallScreenWidget(); } class _MyCallScreenWidget extends State @@ -27,13 +29,17 @@ class _MyCallScreenWidget extends State bool _showNumPad = false; String _timeLabel = '00:00'; - late Timer _timer; bool _audioMuted = false; bool _videoMuted = false; bool _speakerOn = false; bool _hold = false; + bool _mirror = true; String? _holdOriginator; CallStateEnum _state = CallStateEnum.NONE; + + late String _transferTarget; + late Timer _timer; + SIPUAHelper? get helper => widget._helper; bool get voiceOnly => @@ -237,6 +243,9 @@ class _MyCallScreenWidget extends State void _switchCamera() { if (_localStream != null) { Helper.switchCamera(_localStream!.getVideoTracks()[0]); + setState(() { + _mirror = !_mirror; + }); } } @@ -264,7 +273,6 @@ class _MyCallScreenWidget extends State } } - late String _transferTarget; void _handleTransfer() { showDialog( context: context, @@ -324,7 +332,7 @@ class _MyCallScreenWidget extends State } List _buildNumPad() { - var labels = [ + final labels = [ [ {'1': ''}, {'2': 'abc'}, @@ -364,22 +372,22 @@ class _MyCallScreenWidget extends State } Widget _buildActionButtons() { - var hangupBtn = ActionButton( + final hangupBtn = ActionButton( title: "hangup", onPressed: () => _handleHangup(), icon: Icons.call_end, fillColor: Colors.red, ); - var hangupBtnInactive = ActionButton( + final hangupBtnInactive = ActionButton( title: "hangup", onPressed: () {}, icon: Icons.call_end, fillColor: Colors.grey, ); - var basicActions = []; - var advanceActions = []; + final basicActions = []; + final advanceActions = []; switch (_state) { case CallStateEnum.NONE: @@ -472,67 +480,83 @@ class _MyCallScreenWidget extends State break; } - var actionWidgets = []; + final actionWidgets = []; if (_showNumPad) { actionWidgets.addAll(_buildNumPad()); } else { if (advanceActions.isNotEmpty) { - actionWidgets.add(Padding( + actionWidgets.add( + Padding( padding: const EdgeInsets.all(3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: advanceActions))); + children: advanceActions), + ), + ); } } - actionWidgets.add(Padding( + actionWidgets.add( + Padding( padding: const EdgeInsets.all(3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: basicActions))); + children: basicActions), + ), + ); return Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.end, - children: actionWidgets); + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: actionWidgets, + ); } Widget _buildContent() { - var stackWidgets = []; + final stackWidgets = []; if (!voiceOnly && _remoteStream != null) { - stackWidgets.add(Center( - child: RTCVideoView(_remoteRenderer!), - )); + stackWidgets.add( + Center( + child: RTCVideoView( + _remoteRenderer!, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), + ), + ); } if (!voiceOnly && _localStream != null) { - stackWidgets.add(Container( - child: AnimatedContainer( - child: RTCVideoView(_localRenderer!), + stackWidgets.add( + AnimatedContainer( + child: RTCVideoView( + _localRenderer!, + mirror: _mirror, + objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, + ), height: _localVideoHeight, width: _localVideoWidth, alignment: Alignment.topRight, duration: Duration(milliseconds: 300), margin: _localVideoMargin, ), - alignment: Alignment.topRight, - )); + ); } - stackWidgets.addAll([ - Positioned( - top: voiceOnly ? 48 : 6, - left: 0, - right: 0, - child: Center( + stackWidgets.addAll( + [ + Positioned( + top: voiceOnly ? 48 : 6, + left: 0, + right: 0, + child: Center( child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Center( - child: Padding( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Padding( padding: const EdgeInsets.all(6), child: Text( (voiceOnly ? 'VOICE CALL' : 'VIDEO CALL') + @@ -540,23 +564,33 @@ class _MyCallScreenWidget extends State ? ' PAUSED BY ${_holdOriginator!.toUpperCase()}' : ''), style: TextStyle(fontSize: 24, color: Colors.black54), - ))), - Center( - child: Padding( + ), + ), + ), + Center( + child: Padding( padding: const EdgeInsets.all(6), child: Text( '$remoteIdentity', style: TextStyle(fontSize: 18, color: Colors.black54), - ))), - Center( - child: Padding( + ), + ), + ), + Center( + child: Padding( padding: const EdgeInsets.all(6), - child: Text(_timeLabel, - style: TextStyle(fontSize: 14, color: Colors.black54)))) - ], - )), - ), - ]); + child: Text( + _timeLabel, + style: TextStyle(fontSize: 14, color: Colors.black54), + ), + ), + ) + ], + ), + ), + ), + ], + ); return Stack( children: stackWidgets, @@ -566,16 +600,18 @@ class _MyCallScreenWidget extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: Text('[$direction] ${EnumHelper.getName(_state)}')), - body: Container( - child: _buildContent(), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Padding( - padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 24.0), - child: Container(width: 320, child: _buildActionButtons()))); + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text('[$direction] ${EnumHelper.getName(_state)}'), + ), + body: _buildContent(), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Container( + width: 320, + padding: EdgeInsets.only(bottom: 24.0), + child: _buildActionButtons(), + ), + ); } @override diff --git a/example/lib/src/dialpad.dart b/example/lib/src/dialpad.dart index 3eeecbfe..5a27b879 100644 --- a/example/lib/src/dialpad.dart +++ b/example/lib/src/dialpad.dart @@ -9,9 +9,11 @@ import 'widgets/action_button.dart'; class DialPadWidget extends StatefulWidget { final SIPUAHelper? _helper; + DialPadWidget(this._helper, {Key? key}) : super(key: key); + @override - _MyDialPadWidget createState() => _MyDialPadWidget(); + State createState() => _MyDialPadWidget(); } class _MyDialPadWidget extends State @@ -46,7 +48,7 @@ class _MyDialPadWidget extends State Future _handleCall(BuildContext context, [bool voiceOnly = false]) async { - var dest = _textController?.text; + final dest = _textController?.text; if (defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS) { await Permission.microphone.request(); @@ -74,7 +76,14 @@ class _MyDialPadWidget extends State return null; } - final mediaConstraints = {'audio': true, 'video': true}; + final mediaConstraints = { + 'audio': true, + 'video': { + 'width': '1280', + 'height': '720', + 'facingMode': 'user', + } + }; MediaStream mediaStream; @@ -84,9 +93,14 @@ class _MyDialPadWidget extends State mediaConstraints['video'] = false; MediaStream userStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); - mediaStream.addTrack(userStream.getAudioTracks()[0], addToNative: true); + final audioTracks = userStream.getAudioTracks(); + if (audioTracks.isNotEmpty) { + mediaStream.addTrack(audioTracks.first, addToNative: true); + } } else { - mediaConstraints['video'] = !voiceOnly; + if (voiceOnly) { + mediaConstraints['video'] = !voiceOnly; + } mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); } @@ -112,7 +126,7 @@ class _MyDialPadWidget extends State } List _buildNumPad() { - var labels = [ + final labels = [ [ {'1': ''}, {'2': 'abc'}, @@ -153,145 +167,137 @@ class _MyDialPadWidget extends State List _buildDialPad() { return [ + Align( + alignment: AlignmentDirectional.centerStart, + child: Text('Destination URL'), + ), + const SizedBox(height: 8), Container( - width: 360, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 360, - child: TextField( - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 24, color: Colors.black54), - decoration: InputDecoration( - border: InputBorder.none, - ), - controller: _textController, - )), - ])), + width: 500, + child: TextField( + keyboardType: TextInputType.text, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, color: Colors.black54), + maxLines: 3, + decoration: InputDecoration( + border: OutlineInputBorder(), + ), + controller: _textController, + ), + ), Container( - width: 300, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildNumPad())), + width: 500, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildNumPad(), + ), + ), Container( - width: 300, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ActionButton( - icon: Icons.videocam, - onPressed: () => _handleCall(context), - ), - ActionButton( - icon: Icons.dialer_sip, - fillColor: Colors.green, - onPressed: () => _handleCall(context, true), - ), - ActionButton( - icon: Icons.keyboard_arrow_left, - onPressed: () => _handleBackSpace(), - onLongPress: () => _handleBackSpace(true), - ), - ], - ))) + width: 500, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ActionButton( + icon: Icons.videocam, + onPressed: () => _handleCall(context), + ), + ActionButton( + icon: Icons.dialer_sip, + fillColor: Colors.green, + onPressed: () => _handleCall(context, true), + ), + ActionButton( + icon: Icons.keyboard_arrow_left, + onPressed: () => _handleBackSpace(), + onLongPress: () => _handleBackSpace(true), + ), + ], + ), + ), + ), ]; } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Dart SIP UA Demo"), - actions: [ - PopupMenuButton( - onSelected: (String value) { - switch (value) { - case 'account': - Navigator.pushNamed(context, '/register'); - break; - case 'about': - Navigator.pushNamed(context, '/about'); - break; - default: - break; - } - }, - icon: Icon(Icons.menu), - itemBuilder: (BuildContext context) => >[ - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), - child: Icon( - Icons.account_circle, - color: Colors.black38, - ), - ), - SizedBox( - child: Text('Account'), - width: 64, - ) - ], - ), - value: 'account', + appBar: AppBar( + title: Text("Dart SIP UA Demo"), + actions: [ + PopupMenuButton( + onSelected: (String value) { + switch (value) { + case 'account': + Navigator.pushNamed(context, '/register'); + break; + case 'about': + Navigator.pushNamed(context, '/about'); + break; + default: + break; + } + }, + icon: Icon(Icons.menu), + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + child: Row( + children: [ + Icon( + Icons.account_circle, + color: Colors.black54, + ), + SizedBox(width: 12), + Text('Account'), + ], ), - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Icon( - Icons.info, - color: Colors.black38, - ), - SizedBox( - child: Text('About'), - width: 64, - ) - ], - ), - value: 'about', - ) - ]), - ], - ), - body: Align( - alignment: Alignment(0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(6.0), - child: Center( - child: Text( - 'Status: ${EnumHelper.getName(helper!.registerState.state)}', - style: TextStyle(fontSize: 14, color: Colors.black54), - )), - ), - Padding( - padding: const EdgeInsets.all(6.0), - child: Center( - child: Text( - 'Received Message: $receivedMsg', - style: TextStyle(fontSize: 14, color: Colors.black54), - )), - ), - Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: _buildDialPad(), - )), - ]))); + value: 'account', + ), + PopupMenuItem( + child: Row( + children: [ + Icon( + Icons.info, + color: Colors.black54, + ), + SizedBox(width: 12), + Text('About'), + ], + ), + value: 'about', + ) + ]), + ], + ), + body: ListView( + padding: EdgeInsets.symmetric(horizontal: 12), + children: [ + SizedBox(height: 20), + Center( + child: Text( + 'Register Status: ${EnumHelper.getName(helper!.registerState.state)}', + style: TextStyle(fontSize: 18, color: Colors.black54), + ), + ), + SizedBox(height: 12), + Center( + child: Text( + 'Received Message: $receivedMsg', + style: TextStyle(fontSize: 16, color: Colors.black54), + ), + ), + SizedBox(height: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildDialPad(), + ), + ], + ), + ); } @override diff --git a/example/lib/src/register.dart b/example/lib/src/register.dart index 91f5dc5e..62c6de7c 100644 --- a/example/lib/src/register.dart +++ b/example/lib/src/register.dart @@ -4,9 +4,11 @@ import 'package:sip_ua/sip_ua.dart'; class RegisterWidget extends StatefulWidget { final SIPUAHelper? _helper; + RegisterWidget(this._helper, {Key? key}) : super(key: key); + @override - _MyRegisterWidget createState() => _MyRegisterWidget(); + State createState() => _MyRegisterWidget(); } class _MyRegisterWidget extends State @@ -27,7 +29,7 @@ class _MyRegisterWidget extends State SIPUAHelper? get helper => widget._helper; @override - initState() { + void initState() { super.initState(); _registerState = helper!.registerState; helper!.addSipUaHelperListener(this); @@ -35,7 +37,17 @@ class _MyRegisterWidget extends State } @override - deactivate() { + void dispose() { + _passwordController.dispose(); + _wsUriController.dispose(); + _sipUriController.dispose(); + _displayNameController.dispose(); + _authorizationUserController.dispose(); + super.dispose(); + } + + @override + void deactivate() { super.deactivate(); helper!.removeSipUaHelperListener(this); _saveSettings(); @@ -119,167 +131,75 @@ class _MyRegisterWidget extends State @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("SIP Account"), - ), - body: Align( - alignment: Alignment(0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - children: [ - Padding( - padding: - const EdgeInsets.fromLTRB(48.0, 18.0, 48.0, 18.0), - child: Center( - child: Text( - 'Register Status: ${EnumHelper.getName(_registerState.state)}', - style: TextStyle(fontSize: 18, color: Colors.black54), - )), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 18.0, 48.0, 0), - child: Align( - child: Text('WebSocket:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _wsUriController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('SIP URI:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _sipUriController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Authorization User:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _authorizationUserController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - hintText: _authorizationUserController.text.isEmpty - ? '[Empty]' - : null, - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Password:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _passwordController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - hintText: _passwordController.text.isEmpty - ? '[Empty]' - : null, - ), - ), - ), - ], - ), - Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(46.0, 18.0, 48.0, 0), - child: Align( - child: Text('Display Name:'), - alignment: Alignment.centerLeft, - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(48.0, 0.0, 48.0, 0), - child: TextFormField( - controller: _displayNameController, - keyboardType: TextInputType.text, - textAlign: TextAlign.center, - decoration: InputDecoration( - contentPadding: EdgeInsets.all(10.0), - border: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.black12)), - ), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.fromLTRB(0.0, 18.0, 0.0, 0.0), - child: Container( - height: 48.0, - width: 160.0, - child: MaterialButton( - child: Text( - 'Register', - style: - TextStyle(fontSize: 16.0, color: Colors.white), - ), - color: Colors.blue, - textColor: Colors.white, - onPressed: () => _handleSave(context), - ), - )) - ]))); + appBar: AppBar( + title: Text("SIP Account"), + ), + body: ListView( + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 20), + children: [ + Center( + child: Text( + 'Register Status: ${EnumHelper.getName(_registerState.state)}', + style: TextStyle(fontSize: 18, color: Colors.black54), + ), + ), + SizedBox(height: 40), + Text('WebSocket:'), + TextFormField( + controller: _wsUriController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + ), + SizedBox(height: 20), + Text('SIP URI:'), + TextFormField( + controller: _sipUriController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + ), + SizedBox(height: 20), + Text('Authorization User:'), + TextFormField( + controller: _authorizationUserController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: + _authorizationUserController.text.isEmpty ? '[Empty]' : null, + ), + ), + SizedBox(height: 20), + Text('Password:'), + TextFormField( + controller: _passwordController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: _passwordController.text.isEmpty ? '[Empty]' : null, + ), + ), + SizedBox(height: 20), + Text('Display Name:'), + TextFormField( + controller: _displayNameController, + keyboardType: TextInputType.text, + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: _displayNameController.text.isEmpty ? '[Empty]' : null, + ), + ), + const SizedBox(height: 40), + ElevatedButton( + child: Text('Register'), + onPressed: () => _handleSave(context), + ), + ], + ), + ); } @override diff --git a/example/lib/src/widgets/action_button.dart b/example/lib/src/widgets/action_button.dart index ca2bf08f..d2a2cec3 100644 --- a/example/lib/src/widgets/action_button.dart +++ b/example/lib/src/widgets/action_button.dart @@ -23,7 +23,7 @@ class ActionButton extends StatefulWidget { : super(key: key); @override - _ActionButtonState createState() => _ActionButtonState(); + State createState() => _ActionButtonState(); } class _ActionButtonState extends State { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e94de160..611b9d42 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,19 +21,16 @@ environment: dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 sip_ua: path: ../ - shared_preferences: ^2.0.5 - permission_handler: ^9.2.0 + shared_preferences: ^2.2.0 + permission_handler: ^10.4.3 + flutter_webrtc: ^0.9.40 dev_dependencies: flutter_test: sdk: flutter - lints: ^1.0.1 + lints: ^2.1.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 4a073aae2af4e85c33b2fcddba9f8980dfe59416 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 23 Aug 2023 08:43:51 -0500 Subject: [PATCH 07/18] fix: parse expires from string header. (#396) * fix: parse expires from string header. * update. --- lib/src/rtc_session.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 95529750..49d4d152 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -362,7 +362,15 @@ class RTCSession extends EventManager implements Owner { // Get the Expires header value if exists. if (request.hasHeader('expires')) { - expires = request.getHeader('expires') * 1000; + try { + expires = (request.getHeader('expires') is num + ? request.getHeader('expires') + : num.tryParse(request.getHeader('expires'))!) * + 1000; + } catch (e) { + logger.e( + 'Invalid Expires header value: ${request.getHeader('expires')}, error $e'); + } } /* Set the to_tag before From 0d7fb500753a5b78acaaeb7494d63ddd9db19298 Mon Sep 17 00:00:00 2001 From: Victor Uvarov <35702719+VictorUvarov@users.noreply.github.com> Date: Wed, 13 Sep 2023 07:20:14 -0700 Subject: [PATCH 08/18] add support for UAConfiguration registrar_server (#397) - https://github.com/versatica/JsSIP/blob/004d160968ec71982beca46d021e3cab746ac55d/lib/UA.d.ts#L43C3-L43C19 --- lib/src/sip_ua_helper.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index dc0994be..f5980a66 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -142,6 +142,7 @@ class SIPUAHelper extends EventManager { _settings.session_timers_refresh_method = uaSettings.sessionTimersRefreshMethod; _settings.instance_id = uaSettings.instanceId; + _settings.registrar_server = uaSettings.registrarServer; try { _ua = UA(_settings); @@ -711,6 +712,7 @@ class UaSettings { String? ha1; String? displayName; String? instanceId; + String? registrarServer; /// DTMF mode, in band (rfc2833) or out of band (sip info) DtmfMode dtmfMode = DtmfMode.INFO; From d4c0a5b6e8e44085d684751298bc23863b7532e2 Mon Sep 17 00:00:00 2001 From: Matthias Schicker Date: Mon, 11 Dec 2023 14:23:36 +0100 Subject: [PATCH 09/18] Feature/update dependency to intl v19 (#414) * #376: Added `_settings.session_timers_refresh_method` to UaSettings to expose this feature for users of SipUaHelper * Reformatted sip_ua_helper to get build pipeline to run * Updated some dependencies * More dependency updates --------- Co-authored-by: Matthias Schicker --- pubspec.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 26f7edf7..1eebc719 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,17 +7,17 @@ environment: flutter: ">=2.0.0" dependencies: - collection: ^1.15.0 - crypto: ^3.0.0 - flutter_webrtc: ^0.9.25 - intl: ^0.18.0 + collection: ^1.18.0 + crypto: ^3.0.3 + flutter_webrtc: ^0.9.47 + intl: ^0.19.0 logger: ^1.0.0 parser_error: ^0.2.0 path: ^1.6.4 - random_string: ^2.0.0 - recase: ^4.0.0 + random_string: ^2.3.1 + recase: ^4.1.0 sdp_transform: ^0.3.2 - uuid: ^3.0.2 + uuid: ^4.2.1 dev_dependencies: From ec6617230a05fad7db8ff4024eeeb97ce600d5d7 Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Mon, 15 Jan 2024 09:16:43 +0800 Subject: [PATCH 10/18] upgrade intl and logger. --- example/pubspec.yaml | 4 +- lib/src/event_manager/event_manager.dart | 4 +- lib/src/grammar.dart | 3 +- lib/src/parser_error.dart | 113 ++++++++++++++++++++ lib/src/rtc_session.dart | 7 +- lib/src/sip_ua_helper.dart | 8 +- lib/src/transport.dart | 4 +- lib/src/transports/websocket_interface.dart | 2 +- lib/src/ua.dart | 8 +- pubspec.yaml | 6 +- 10 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 lib/src/parser_error.dart diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 611b9d42..c93c5de2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -24,13 +24,13 @@ dependencies: sip_ua: path: ../ shared_preferences: ^2.2.0 - permission_handler: ^10.4.3 + permission_handler: ^11.1.0 flutter_webrtc: ^0.9.40 dev_dependencies: flutter_test: sdk: flutter - lints: ^2.1.1 + lints: ^3.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/lib/src/event_manager/event_manager.dart b/lib/src/event_manager/event_manager.dart index bfa92966..aa84e7b0 100644 --- a/lib/src/event_manager/event_manager.dart +++ b/lib/src/event_manager/event_manager.dart @@ -69,7 +69,7 @@ class EventManager { targets.remove(listener); targets.add(listener); } catch (e, s) { - logger.e(e.toString(), null, s); + logger.e(e.toString(), error: e, stackTrace: s); } } @@ -108,7 +108,7 @@ class EventManager { // logger.w("invoking $event on $target"); target(event); } catch (e, s) { - logger.e(e.toString(), null, s); + logger.e(e.toString(), error: e, stackTrace: s); } } } diff --git a/lib/src/grammar.dart b/lib/src/grammar.dart index f4192c54..fb2feb8b 100644 --- a/lib/src/grammar.dart +++ b/lib/src/grammar.dart @@ -1,6 +1,5 @@ -import 'package:parser_error/parser_error.dart'; - import 'grammar_parser.dart'; +import 'parser_error.dart'; class Grammar { static dynamic parse(String input, String startRule) { diff --git a/lib/src/parser_error.dart b/lib/src/parser_error.dart new file mode 100644 index 00000000..bc80928f --- /dev/null +++ b/lib/src/parser_error.dart @@ -0,0 +1,113 @@ +import 'package:text/text.dart'; + +class ParserErrorMessage { + ParserErrorMessage(this.message, this.start, this.end) { + if (end < 0) { + throw ArgumentError.value(end, 'end'); + } + + if (start < 0 || start > end) { + throw ArgumentError.value(start, 'start'); + } + } + + /// End position of error. + final int end; + + /// Error message. + final String message; + + /// Start position of error. + final int start; +} + +class ParserErrorFormatter { + /// Returns formatted error as strings. + /// + /// Parameters: + /// [String] source + /// Text of source code. + /// + /// [List]<[ParserErrorMessage]> error + /// List of parser error messages. + /// + /// [int] lineLimit + /// Length limit of the formatted line. + /// + /// [int] offset + /// Offset to be added to the values "start" and "end". + /// + /// [String] title + /// Title of parser error + static List format(String source, List messages, + {int lineLimit = 80, int offset = 0, String title = 'Format exception'}) { + if (lineLimit < 1) { + throw ArgumentError.value(lineLimit, 'lineLimit'); + } + + if (offset < 0) { + throw ArgumentError.value(offset, 'offset'); + } + + final List result = []; + final Text text = Text(source); + final int sourceLength = source.length; + for (ParserErrorMessage error in messages) { + int position = error.end + offset; + if (error.start != error.end) { + position = error.start + offset; + } + + Location? location; + Line? line; + String locationString = ''; + if (position < sourceLength) { + line = text.lineAt(position); + location = text.locationAt(position); + locationString = ' (${location.toString()})'; + } + + result.add('$title$locationString: ${error.message}'); + if (line != null) { + String string = String.fromCharCodes(line.characters); + string = string.replaceAll('\n', ''); + string = string.replaceAll('\r', ''); + int indicatorLength = 1; + int indicatorPosition = 0; + if (location != null) { + indicatorPosition = location.column - 1; + } + if (error.end != error.start) { + indicatorLength = error.end - error.start; + } + + if (indicatorLength > lineLimit) { + indicatorLength = lineLimit; + } + + if (indicatorPosition + indicatorLength > lineLimit) { + if (indicatorPosition < lineLimit || indicatorLength < lineLimit) { + final int delta = (indicatorPosition + indicatorLength) - lineLimit; + string = string.substring(delta); + indicatorPosition -= delta; + } else { + string = string.substring(indicatorPosition); + indicatorPosition = 0; + } + } + + if (string.length > lineLimit) { + string = string.substring(0, lineLimit); + } + + final String prefix = ''.padRight(indicatorPosition, ' '); + final String suffix = ''.padRight(indicatorLength, '^'); + final String indicator = '$prefix$suffix'; + result.add(string); + result.add(indicator); + } + } + + return result; + } +} diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 49d4d152..0cd11daf 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -692,7 +692,8 @@ class RTCSession extends EventManager implements Owner { if (_status == C.STATUS_TERMINATED) { return; } - logger.e('Failed to answer(): ${error.toString()}', error, s); + logger.e('Failed to answer(): ${error.toString()}', + error: error, stackTrace: s); } } @@ -2293,7 +2294,7 @@ class RTCSession extends EventManager implements Owner { request_sender.send(); } catch (error, s) { - logger.e(error.toString(), null, s); + logger.e(error.toString(), error: error, stackTrace: s); _failed('local', null, null, null, 500, DartSIP_C.CausesType.WEBRTC_ERROR, 'Can\'t create local SDP'); if (_status == C.STATUS_TERMINATED) { @@ -2559,7 +2560,7 @@ class RTCSession extends EventManager implements Owner { 'eventHandlers': handlers }); } catch (e, s) { - logger.e(e.toString(), null, s); + logger.e(e.toString(), error: e, stackTrace: s); onFailed(); } } diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index f5980a66..22aec9c1 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -100,8 +100,8 @@ class SIPUAHelper extends EventManager { _ua!.call(target, options); return true; } else { - logger.e( - 'Not connected, you will need to register.', null, StackTraceNJ()); + logger.e('Not connected, you will need to register.', + stackTrace: StackTraceNJ()); } return false; } @@ -212,8 +212,8 @@ class SIPUAHelper extends EventManager { }); _ua!.start(); - } catch (event, s) { - logger.e(event.toString(), null, s); + } catch (e, s) { + logger.e(e.toString(), error: e, stackTrace: s); } } diff --git a/lib/src/transport.dart b/lib/src/transport.dart index af3b93af..a93f3b0f 100644 --- a/lib/src/transport.dart +++ b/lib/src/transport.dart @@ -147,8 +147,8 @@ class Transport { if (!isConnected()) { logger.e( 'unable to send message, transport is not connected. Current state is $status', - null, - StackTraceNJ()); + error: e, + stackTrace: StackTraceNJ()); return false; } diff --git a/lib/src/transports/websocket_interface.dart b/lib/src/transports/websocket_interface.dart index 36651612..a7c11a23 100644 --- a/lib/src/transports/websocket_interface.dart +++ b/lib/src/transports/websocket_interface.dart @@ -109,7 +109,7 @@ class WebSocketInterface implements Socket { protocols: [_websocket_protocol], webSocketSettings: _webSocketSettings); } catch (e, s) { - logger.e(e.toString(), null, s); + logger.e(e.toString(), error: e, stackTrace: s); _connected = false; logger.e('WebSocket $_url error: $e'); } diff --git a/lib/src/ua.dart b/lib/src/ua.dart index 581a6ee8..ad467116 100644 --- a/lib/src/ua.dart +++ b/lib/src/ua.dart @@ -330,8 +330,8 @@ class UA extends EventManager { if (!rtcSession.isEnded()) { rtcSession.terminate(); } - } catch (error, s) { - logger.e(error.toString(), null, s); + } catch (e, s) { + logger.e(e.toString(), error: e, stackTrace: s); } } }); @@ -343,8 +343,8 @@ class UA extends EventManager { try { Subscriber subscriber = _subscribers[key]!; subscriber.terminate(null); - } catch (error, s) { - logger.e(error.toString(), null, s); + } catch (e, s) { + logger.e(e.toString(), error: e, stackTrace: s); } } }); diff --git a/pubspec.yaml b/pubspec.yaml index 1eebc719..2fde5bca 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,15 +11,15 @@ dependencies: crypto: ^3.0.3 flutter_webrtc: ^0.9.47 intl: ^0.19.0 - logger: ^1.0.0 - parser_error: ^0.2.0 + logger: ^2.0.2+1 path: ^1.6.4 random_string: ^2.3.1 recase: ^4.1.0 sdp_transform: ^0.3.2 + text: ^0.2.0 uuid: ^4.2.1 dev_dependencies: - lints: ^2.0.1 + lints: ^3.0.0 test: ^1.6.7 From 024d0e847dba866e7ff80ce1c518091fe0e7496c Mon Sep 17 00:00:00 2001 From: Mikael Wills <63661422+mikaelwills@users.noreply.github.com> Date: Wed, 8 May 2024 15:13:28 +0100 Subject: [PATCH 11/18] Tcp socket implementation (#416) Co-authored-by: Mikael Wills --- example/lib/src/register.dart | 66 +++++-- lib/sip_ua.dart | 1 + lib/src/config.dart | 23 ++- lib/src/event_manager/event_manager.dart | 4 +- lib/src/event_manager/transport_events.dart | 11 +- lib/src/logger.dart | 2 +- lib/src/registrator.dart | 8 +- lib/src/request_sender.dart | 10 +- lib/src/rtc_session.dart | 43 +++-- lib/src/rtc_session/dtmf.dart | 1 - lib/src/sanity_check.dart | 6 +- lib/src/sip_message.dart | 8 +- lib/src/sip_ua_helper.dart | 60 ++++-- lib/src/socket.dart | 20 -- .../{transport.dart => socket_transport.dart} | 72 +++---- lib/src/transactions/ack_client.dart | 6 +- lib/src/transactions/invite_client.dart | 6 +- lib/src/transactions/invite_server.dart | 4 +- lib/src/transactions/non_invite_client.dart | 4 +- lib/src/transactions/non_invite_server.dart | 4 +- lib/src/transactions/transaction_base.dart | 4 +- lib/src/transport_constants.dart | 19 ++ lib/src/transport_type.dart | 1 + lib/src/transports/socket_interface.dart | 18 ++ lib/src/transports/tcp_socket.dart | 179 ++++++++++++++++++ lib/src/transports/tcp_socket_impl.dart | 75 ++++++++ ...bsocket_interface.dart => web_socket.dart} | 31 +-- lib/src/transports/websocket_dart_impl.dart | 6 +- lib/src/transports/websocket_web_impl.dart | 4 +- lib/src/ua.dart | 63 +++--- test/test_sip_ua.dart | 7 +- test/test_websocket.dart | 26 +-- 32 files changed, 574 insertions(+), 218 deletions(-) delete mode 100644 lib/src/socket.dart rename lib/src/{transport.dart => socket_transport.dart} (80%) create mode 100644 lib/src/transport_constants.dart create mode 100644 lib/src/transport_type.dart create mode 100644 lib/src/transports/socket_interface.dart create mode 100644 lib/src/transports/tcp_socket.dart create mode 100644 lib/src/transports/tcp_socket_impl.dart rename lib/src/transports/{websocket_interface.dart => web_socket.dart} (89%) diff --git a/example/lib/src/register.dart b/example/lib/src/register.dart index 62c6de7c..3081c0bb 100644 --- a/example/lib/src/register.dart +++ b/example/lib/src/register.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sip_ua/sip_ua.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; class RegisterWidget extends StatefulWidget { final SIPUAHelper? _helper; @@ -14,6 +15,7 @@ class RegisterWidget extends StatefulWidget { class _MyRegisterWidget extends State implements SipUaHelperListener { final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _portController = TextEditingController(); final TextEditingController _wsUriController = TextEditingController(); final TextEditingController _sipUriController = TextEditingController(); final TextEditingController _displayNameController = TextEditingController(); @@ -26,6 +28,8 @@ class _MyRegisterWidget extends State late SharedPreferences _preferences; late RegistrationState _registerState; + TransportType _selectedTransport = TransportType.TCP; + SIPUAHelper? get helper => widget._helper; @override @@ -34,6 +38,9 @@ class _MyRegisterWidget extends State _registerState = helper!.registerState; helper!.addSipUaHelperListener(this); _loadSettings(); + if (kIsWeb) { + _selectedTransport = TransportType.WS; + } } @override @@ -56,6 +63,7 @@ class _MyRegisterWidget extends State void _loadSettings() async { _preferences = await SharedPreferences.getInstance(); setState(() { + _portController.text = '5060'; _wsUriController.text = _preferences.getString('ws_uri') ?? 'wss://tryit.jssip.net:10443'; _sipUriController.text = @@ -69,6 +77,7 @@ class _MyRegisterWidget extends State } void _saveSettings() { + _preferences.setString('port', _portController.text); _preferences.setString('ws_uri', _wsUriController.text); _preferences.setString('sip_uri', _sipUriController.text); _preferences.setString('display_name', _displayNameController.text); @@ -113,12 +122,15 @@ class _MyRegisterWidget extends State UaSettings settings = UaSettings(); - settings.webSocketUrl = _wsUriController.text; + settings.port = _portController.text; settings.webSocketSettings.extraHeaders = _wsExtraHeaders; settings.webSocketSettings.allowBadCertificate = true; //settings.webSocketSettings.userAgent = 'Dart/2.8 (dart:io) for OpenSIPS.'; - + settings.tcpSocketSettings.allowBadCertificate = true; + settings.transportType = _selectedTransport; settings.uri = _sipUriController.text; + settings.webSocketUrl = _wsUriController.text; + settings.host = _sipUriController.text.split('@')[1]; settings.authorizationUser = _authorizationUserController.text; settings.password = _passwordController.text; settings.displayName = _displayNameController.text; @@ -143,14 +155,24 @@ class _MyRegisterWidget extends State style: TextStyle(fontSize: 18, color: Colors.black54), ), ), - SizedBox(height: 40), - Text('WebSocket:'), - TextFormField( - controller: _wsUriController, - keyboardType: TextInputType.text, - autocorrect: false, - textAlign: TextAlign.center, - ), + SizedBox(height: 20), + if (_selectedTransport == TransportType.WS) ...[ + Text('WebSocket:'), + TextFormField( + controller: _wsUriController, + keyboardType: TextInputType.text, + autocorrect: false, + textAlign: TextAlign.center, + ), + ], + if (_selectedTransport == TransportType.TCP) ...[ + Text('Port:'), + TextFormField( + controller: _portController, + keyboardType: TextInputType.text, + textAlign: TextAlign.center, + ), + ], SizedBox(height: 20), Text('SIP URI:'), TextFormField( @@ -192,7 +214,29 @@ class _MyRegisterWidget extends State hintText: _displayNameController.text.isEmpty ? '[Empty]' : null, ), ), - const SizedBox(height: 40), + const SizedBox(height: 20), + if (!kIsWeb) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RadioMenuButton( + value: TransportType.TCP, + groupValue: _selectedTransport, + onChanged: ((value) => setState(() { + _selectedTransport = value!; + })), + child: Text("TCP")), + RadioMenuButton( + value: TransportType.WS, + groupValue: _selectedTransport, + onChanged: ((value) => setState(() { + _selectedTransport = value!; + })), + child: Text("WS")), + ], + ), + ], + const SizedBox(height: 20), ElevatedButton( child: Text('Register'), onPressed: () => _handleSave(context), diff --git a/lib/sip_ua.dart b/lib/sip_ua.dart index 3be43a90..03d5fc00 100644 --- a/lib/sip_ua.dart +++ b/lib/sip_ua.dart @@ -1,3 +1,4 @@ /// only expose the bare minimum of internals required export 'src/enum_helper.dart'; export 'src/sip_ua_helper.dart'; +export 'src/transport_type.dart'; diff --git a/lib/src/config.dart b/lib/src/config.dart index 6b23ed98..a38804bb 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -1,11 +1,12 @@ import 'package:sip_ua/sip_ua.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/tcp_socket.dart'; import 'constants.dart' as DartSIP_C; import 'constants.dart'; import 'exceptions.dart' as Exceptions; import 'grammar.dart'; import 'logger.dart'; -import 'socket.dart' as Socket; -import 'transports/websocket_interface.dart'; +import 'transports/web_socket.dart'; import 'uri.dart'; import 'utils.dart' as Utils; @@ -43,8 +44,10 @@ class Settings { // Dtmf mode DtmfMode dtmf_mode = DtmfMode.INFO; + TransportType? transportType; + // Connection options. - List? sockets = []; + List? sockets = []; int connection_recovery_max_interval = 30; int connection_recovery_min_interval = 2; @@ -71,16 +74,17 @@ class Checks { Map mandatory = { 'sockets': (Settings src, Settings? dst) { - List? sockets = src.sockets; + List? sockets = src.sockets; + /* Allow defining sockets parameter as: * Socket: socket * List of Socket: [socket1, socket2] * List of Objects: [{socket: socket1, weight:1}, {socket: Socket2, weight:0}] * List of Objects and Socket: [{socket: socket1}, socket2] */ - List copy = []; + List copy = []; if (sockets is List && sockets!.length > 0) { - for (WebSocketInterface socket in sockets) { + for (SIPUASocketInterface socket in sockets) { copy.add(socket); } } else { @@ -105,6 +109,13 @@ class Checks { } else { dst!.uri = parsed; } + }, + 'transport_type': (Settings src, Settings? dst) { + dynamic transportType = src.transportType; + if (src.transportType == null && dst!.transportType == null) { + throw Exceptions.ConfigurationError('transport type', null); + } + dst!.transportType = transportType; } }; Map optional = diff --git a/lib/src/event_manager/event_manager.dart b/lib/src/event_manager/event_manager.dart index aa84e7b0..7fd8a9f6 100644 --- a/lib/src/event_manager/event_manager.dart +++ b/lib/src/event_manager/event_manager.dart @@ -69,7 +69,7 @@ class EventManager { targets.remove(listener); targets.add(listener); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); } } @@ -108,7 +108,7 @@ class EventManager { // logger.w("invoking $event on $target"); target(event); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); } } } diff --git a/lib/src/event_manager/transport_events.dart b/lib/src/event_manager/transport_events.dart index 1c128662..881a9d2d 100644 --- a/lib/src/event_manager/transport_events.dart +++ b/lib/src/event_manager/transport_events.dart @@ -1,18 +1,19 @@ -import '../transports/websocket_interface.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import '../transports/web_socket.dart'; import 'events.dart'; class EventSocketConnected extends EventType { EventSocketConnected({this.socket}); - WebSocketInterface? socket; + SIPUASocketInterface? socket; } class EventSocketConnecting extends EventType { EventSocketConnecting({this.socket}); - WebSocketInterface? socket; + SIPUASocketInterface? socket; } class EventSocketDisconnected extends EventType { - EventSocketDisconnected({WebSocketInterface? socket, this.cause}); - WebSocketInterface? socket; + EventSocketDisconnected({SIPUASocketInterface? socket, this.cause}); + SIPUASocketInterface? socket; ErrorCause? cause; } diff --git a/lib/src/logger.dart b/lib/src/logger.dart index a1a107c6..d991b757 100644 --- a/lib/src/logger.dart +++ b/lib/src/logger.dart @@ -130,7 +130,7 @@ class AnsiColor { String call(String msg) { if (color) { - return '$this$msg$ansiDefault'; + return '$msg$ansiDefault'; } else { return msg; } diff --git a/lib/src/registrator.dart b/lib/src/registrator.dart index 843d992c..806bfbf7 100644 --- a/lib/src/registrator.dart +++ b/lib/src/registrator.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:sip_ua/src/socket_transport.dart'; import 'constants.dart' as DartSIP_C; import 'constants.dart'; import 'event_manager/event_manager.dart'; @@ -10,7 +11,6 @@ import 'name_addr_header.dart'; import 'request_sender.dart'; import 'sip_message.dart'; import 'timers.dart'; -import 'transport.dart'; import 'ua.dart'; import 'uri.dart'; import 'utils.dart' as utils; @@ -24,7 +24,7 @@ class UnHandledResponse { } class Registrator { - Registrator(UA ua, [Transport? transport]) { + Registrator(UA ua, [SocketTransport? transport]) { int reg_id = 1; // Force reg_id to 1. _ua = ua; @@ -71,7 +71,7 @@ class Registrator { } late UA _ua; - Transport? _transport; + SocketTransport? _transport; late URI _registrar; int? _expires; String? _call_id; @@ -86,7 +86,7 @@ class Registrator { bool get registered => _registered; - Transport? get transport => _transport; + SocketTransport? get transport => _transport; void setExtraHeaders(List? extraHeaders) { _extraHeaders = extraHeaders ?? []; diff --git a/lib/src/request_sender.dart b/lib/src/request_sender.dart index 210e77b1..1d2f53cc 100644 --- a/lib/src/request_sender.dart +++ b/lib/src/request_sender.dart @@ -59,16 +59,16 @@ class RequestSender { switch (_method) { case SipMethod.INVITE: - clientTransaction = - InviteClientTransaction(_ua, _ua.transport!, _request!, handlers); + clientTransaction = InviteClientTransaction( + _ua, _ua.socketTransport!, _request!, handlers); break; case SipMethod.ACK: - clientTransaction = - AckClientTransaction(_ua, _ua.transport!, _request!, handlers); + clientTransaction = AckClientTransaction( + _ua, _ua.socketTransport!, _request!, handlers); break; default: clientTransaction = NonInviteClientTransaction( - _ua, _ua.transport!, _request!, handlers); + _ua, _ua.socketTransport!, _request!, handlers); } clientTransaction?.send(); diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 0cd11daf..6ec5fa1c 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -1,8 +1,9 @@ import 'dart:async'; - +import 'dart:convert'; +import 'package:crypto/crypto.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; - +import 'package:sdp_transform/sdp_transform.dart'; import 'package:sip_ua/sip_ua.dart'; import 'constants.dart' as DartSIP_C; import 'constants.dart'; @@ -692,8 +693,7 @@ class RTCSession extends EventManager implements Owner { if (_status == C.STATUS_TERMINATED) { return; } - logger.e('Failed to answer(): ${error.toString()}', - error: error, stackTrace: s); + logger.e('Failed to answer(): ${error.toString()}', error, s); } } @@ -1951,27 +1951,28 @@ class RTCSession extends EventManager implements Owner { } Future _processInDialogSdpOffer( - dynamic request) async { + IncomingRequest request) async { logger.d('_processInDialogSdpOffer()'); - Map sdp = request.parseSDP(); + Map? sdp = request.parseSDP(); bool hold = false; + if (sdp != null) { + for (Map m in sdp['media']) { + if (holdMediaTypes.indexOf(m['type']) == -1) { + continue; + } - for (Map m in sdp['media']) { - if (holdMediaTypes.indexOf(m['type']) == -1) { - continue; - } - - String direction = m['direction'] ?? sdp['direction'] ?? 'sendrecv'; + String direction = m['direction'] ?? sdp['direction'] ?? 'sendrecv'; - if (direction == 'sendonly' || direction == 'inactive') { - hold = true; - } - // If at least one of the streams is active don't emit 'hold'. - else { - hold = false; - break; + if (direction == 'sendonly' || direction == 'inactive') { + hold = true; + } + // If at least one of the streams is active don't emit 'hold'. + else { + hold = false; + break; + } } } @@ -2294,7 +2295,7 @@ class RTCSession extends EventManager implements Owner { request_sender.send(); } catch (error, s) { - logger.e(error.toString(), error: error, stackTrace: s); + logger.e(error.toString(), error, s); _failed('local', null, null, null, 500, DartSIP_C.CausesType.WEBRTC_ERROR, 'Can\'t create local SDP'); if (_status == C.STATUS_TERMINATED) { @@ -2560,7 +2561,7 @@ class RTCSession extends EventManager implements Owner { 'eventHandlers': handlers }); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); onFailed(); } } diff --git a/lib/src/rtc_session/dtmf.dart b/lib/src/rtc_session/dtmf.dart index a5454fae..df8269e3 100644 --- a/lib/src/rtc_session/dtmf.dart +++ b/lib/src/rtc_session/dtmf.dart @@ -51,7 +51,6 @@ class DTMF extends EventManager { throw Exceptions.InvalidStateError(_session.status); } - print(options); List extraHeaders = options['extraHeaders'] != null ? Utils.cloneArray(options['extraHeaders']) : []; diff --git a/lib/src/sanity_check.dart b/lib/src/sanity_check.dart index 09d8cb76..112672f0 100644 --- a/lib/src/sanity_check.dart +++ b/lib/src/sanity_check.dart @@ -4,7 +4,7 @@ import 'logger.dart'; import 'sip_message.dart'; import 'transactions/invite_server.dart'; import 'transactions/non_invite_server.dart'; -import 'transport.dart'; +import 'socket_transport.dart'; import 'ua.dart'; import 'utils.dart' as Utils; @@ -28,9 +28,9 @@ const List responses = [ // local variables. late IncomingMessage message; late UA ua; -late Transport transport; +late SocketTransport transport; -bool sanityCheck(IncomingMessage m, UA u, Transport t) { +bool sanityCheck(IncomingMessage m, UA u, SocketTransport t) { message = m; ua = u; transport = t; diff --git a/lib/src/sip_message.dart b/lib/src/sip_message.dart index e056ba24..e243679d 100644 --- a/lib/src/sip_message.dart +++ b/lib/src/sip_message.dart @@ -10,7 +10,7 @@ import 'exceptions.dart' as Exceptions; import 'grammar.dart'; import 'logger.dart'; import 'name_addr_header.dart'; -import 'transport.dart'; +import 'socket_transport.dart'; import 'ua.dart'; import 'uri.dart'; import 'utils.dart' as utils; @@ -47,7 +47,9 @@ class OutgoingRequest { if (params['route_set'] != null) { setHeader('route', params['route_set']); } else if (ua.configuration.use_preloaded_route) { - setHeader('route', '<${ua.transport!.sip_uri};lr>'); + if (ua.socketTransport != null) { + setHeader('route', '<${ua.socketTransport!.sip_uri};lr>'); + } } // Via. @@ -521,7 +523,7 @@ class IncomingRequest extends IncomingMessage { } UA? ua; URI? ruri; - Transport? transport; + SocketTransport? transport; TransactionBase? server_transaction; /** * Stateful reply. diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index 22aec9c1..3bd8feaf 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -2,8 +2,11 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:logger/logger.dart'; +import 'package:sip_ua/sip_ua.dart'; import 'package:sip_ua/src/map_helper.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/tcp_socket.dart'; import 'config.dart'; import 'constants.dart' as DartSIP_C; import 'event_manager/event_manager.dart'; @@ -15,7 +18,7 @@ import 'rtc_session/refer_subscriber.dart'; import 'sip_message.dart'; import 'stack_trace_nj.dart'; import 'subscriber.dart'; -import 'transports/websocket_interface.dart'; +import 'transports/web_socket.dart'; import 'ua.dart'; class SIPUAHelper extends EventManager { @@ -51,9 +54,11 @@ class SIPUAHelper extends EventManager { } bool get connecting { - if (_ua != null && _ua!.transport != null) { - return _ua!.transport!.isConnecting(); - } + if (_ua == null) return false; + + if (_ua!.socketTransport != null) + return _ua!.socketTransport!.isConnecting(); + return false; } @@ -100,8 +105,7 @@ class SIPUAHelper extends EventManager { _ua!.call(target, options); return true; } else { - logger.e('Not connected, you will need to register.', - stackTrace: StackTraceNJ()); + logger.e('Not connected, you will need to register.', StackTraceNJ()); } return false; } @@ -120,10 +124,25 @@ class SIPUAHelper extends EventManager { // Reset settings _settings = Settings(); - WebSocketInterface socket = WebSocketInterface(uaSettings.webSocketUrl, - messageDelay: _settings.sip_message_delay, - webSocketSettings: uaSettings.webSocketSettings); - _settings.sockets = [socket]; + + _settings.sockets = []; + + if (uaSettings.transportType == TransportType.TCP) { + SIPUATcpSocket socket = SIPUATcpSocket( + uaSettings.host ?? '0.0.0.0', uaSettings.port ?? '5060', + messageDelay: 1); + _settings.sockets!.add(socket); + } + + if (uaSettings.transportType == TransportType.WS) { + SIPUAWebSocket socket = SIPUAWebSocket( + uaSettings.webSocketUrl ?? 'wss://tryit.jssip.net:10443', + messageDelay: _settings.sip_message_delay, + webSocketSettings: uaSettings.webSocketSettings); + _settings.sockets!.add(socket); + } + + _settings.transportType = uaSettings.transportType!; _settings.uri = uaSettings.uri; _settings.sip_message_delay = uaSettings.sip_message_delay; _settings.realm = uaSettings.realm; @@ -213,7 +232,7 @@ class SIPUAHelper extends EventManager { _ua!.start(); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); } } @@ -684,14 +703,26 @@ class WebSocketSettings { String? transport_scheme; } +class TcpSocketSettings { + /// Add additional HTTP headers, such as:'Origin','Host' or others + Map extraHeaders = {}; + + /// `User Agent` field for dart http client. + String? userAgent; + + /// Don‘t check the server certificate + /// for self-signed certificate. + bool allowBadCertificate = false; +} + enum DtmfMode { INFO, RFC2833, } class UaSettings { - late String webSocketUrl; WebSocketSettings webSocketSettings = WebSocketSettings(); + TcpSocketSettings tcpSocketSettings = TcpSocketSettings(); /// May not need to register if on a static IP, just Auth /// Default is true @@ -705,7 +736,10 @@ class UaSettings { /// `User Agent` field for sip message. String? userAgent; + String? host; + String? port; String? uri; + String? webSocketUrl; String? realm; String? authorizationUser; String? password; @@ -714,6 +748,8 @@ class UaSettings { String? instanceId; String? registrarServer; + TransportType? transportType; + /// DTMF mode, in band (rfc2833) or out of band (sip info) DtmfMode dtmfMode = DtmfMode.INFO; diff --git a/lib/src/socket.dart b/lib/src/socket.dart deleted file mode 100644 index dd342928..00000000 --- a/lib/src/socket.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'grammar.dart'; -import 'logger.dart'; -import 'transports/websocket_interface.dart'; -import 'utils.dart' as Utils; - -/// Socket Interface. -abstract class Socket { - late String via_transport; - String? get url; - String? get sip_uri; - - void connect(); - void disconnect(); - void send(dynamic data); - - void Function()? onconnect; - void Function(WebSocketInterface socket, bool error, int? closeCode, - String? reason)? ondisconnect; - void Function(dynamic data)? ondata; -} diff --git a/lib/src/transport.dart b/lib/src/socket_transport.dart similarity index 80% rename from lib/src/transport.dart rename to lib/src/socket_transport.dart index a93f3b0f..b6321723 100644 --- a/lib/src/transport.dart +++ b/lib/src/socket_transport.dart @@ -2,67 +2,49 @@ import 'dart:async'; import 'dart:math'; import 'package:sip_ua/src/event_manager/events.dart'; +import 'package:sip_ua/src/transport_constants.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/tcp_socket.dart'; import 'exceptions.dart' as Exceptions; import 'logger.dart'; -import 'socket.dart' as Socket; import 'stack_trace_nj.dart'; import 'timers.dart'; -import 'transports/websocket_interface.dart'; +import 'transports/web_socket.dart'; import 'utils.dart'; -/** - * Constants - */ -class C { - // Transport status. - static const int STATUS_CONNECTED = 0; - static const int STATUS_CONNECTING = 1; - static const int STATUS_DISCONNECTED = 2; - - // Socket status. - static const int SOCKET_STATUS_READY = 0; - static const int SOCKET_STATUS_ERROR = 1; - - // Recovery options. - static const Map recovery_options = { - 'min_interval': 2, // minimum interval in seconds between recover attempts - 'max_interval': 30 // maximum interval in seconds between recover attempts - }; -} - /* * Manages one or multiple DartSIP.Socket instances. * Is reponsible for transport recovery logic among all socket instances. * * @socket DartSIP::Socket instance */ -class Transport { - Transport(List sockets, +class SocketTransport { + SocketTransport(List? sockets, [Map recovery_options = C.recovery_options]) { - logger.d('new()'); + logger.d('Socket Transport new()'); _recovery_options = recovery_options; // We must recieve at least 1 socket - if (sockets.length == 0) { - throw Exceptions.TypeError('invalid argument: sockets'); + if (sockets!.length == 0) { + throw Exceptions.TypeError( + 'invalid argument: Must recieve atleast 1 web socket'); } - for (WebSocketInterface socket in sockets) { + for (SIPUASocketInterface socket in sockets) { _socketsMap.add({ 'socket': socket, 'weight': socket.weight ?? 0, 'status': C.SOCKET_STATUS_READY }); } - // Get the socket with higher weight. _getSocket(); } int status = C.STATUS_DISCONNECTED; // Current socket. - late WebSocketInterface socket; + late SIPUASocketInterface socket; // Socket collection. final List> _socketsMap = >[]; late Map _recovery_options; @@ -70,10 +52,11 @@ class Transport { Timer? _recovery_timer; bool _close_requested = false; - late void Function(WebSocketInterface? socket, int? attempts) onconnecting; - late void Function(WebSocketInterface? socket, ErrorCause cause) ondisconnect; - late void Function(Transport transport) onconnect; - late void Function(Transport transport, String messageData) ondata; + late void Function(SIPUASocketInterface? socket, int? attempts) onconnecting; + late void Function(SIPUASocketInterface? socket, ErrorCause cause) + ondisconnect; + late void Function(SocketTransport transport) onconnect; + late void Function(SocketTransport transport, String messageData) ondata; /** * Instance Methods @@ -86,7 +69,7 @@ class Transport { String? get sip_uri => socket.sip_uri; void connect() { - logger.d('connect()'); + logger.d('Transport connect()'); if (isConnected()) { logger.d('Transport is already connected'); @@ -113,7 +96,7 @@ class Transport { } void disconnect() { - logger.d('close()'); + logger.d('Transport close()'); _close_requested = true; _recover_attempts = 0; @@ -127,7 +110,7 @@ class Transport { // Unbind socket event callbacks. socket.onconnect = () => () {}; - socket.ondisconnect = (WebSocketInterface socket, bool error, + socket.ondisconnect = (SIPUASocketInterface socket, bool error, int? closeCode, String? reason) => () {}; socket.ondata = (dynamic data) => () {}; @@ -142,18 +125,18 @@ class Transport { } bool send(dynamic data) { - logger.d('send()'); + logger.d('Socket Transport send()'); if (!isConnected()) { logger.e( 'unable to send message, transport is not connected. Current state is $status', - error: e, - stackTrace: StackTraceNJ()); + null, + StackTraceNJ()); + return false; } - String message = data.toString(); - //logger.d('sending message:\n\n$message\n'); + message.split('fingerprint'); return socket.send(message); } @@ -249,7 +232,7 @@ class Transport { } void _onDisconnect( - WebSocketInterface socket, bool error, int? closeCode, String? reason) { + SIPUASocketInterface socket, bool error, int? closeCode, String? reason) { status = C.STATUS_DISCONNECTED; ondisconnect( socket, @@ -275,8 +258,10 @@ class Transport { // CRLF Keep Alive response from server. Ignore it. if (data == '\r\n') { logger.d('received message with CRLF Keep Alive response'); + return; } + // Binary message. else if (data is! String) { try { @@ -287,7 +272,6 @@ class Transport { ' message discarded'); return; } - logger.d('received binary message:\n\n$data\n'); } diff --git a/lib/src/transactions/ack_client.dart b/lib/src/transactions/ack_client.dart index 59401890..e57b39aa 100644 --- a/lib/src/transactions/ack_client.dart +++ b/lib/src/transactions/ack_client.dart @@ -2,14 +2,14 @@ import 'package:sip_ua/src/sip_message.dart'; import '../event_manager/event_manager.dart'; import '../event_manager/internal_events.dart'; import '../logger.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; import '../utils.dart'; import 'transaction_base.dart'; class AckClientTransaction extends TransactionBase { - AckClientTransaction(UA ua, Transport transport, OutgoingRequest request, - EventManager eventHandlers) { + AckClientTransaction(UA ua, SocketTransport transport, + OutgoingRequest request, EventManager eventHandlers) { id = 'z9hG4bK${(Math.random() * 10000000).floor()}'; this.transport = transport; this.request = request; diff --git a/lib/src/transactions/invite_client.dart b/lib/src/transactions/invite_client.dart index aafc80d9..7ee4d86e 100644 --- a/lib/src/transactions/invite_client.dart +++ b/lib/src/transactions/invite_client.dart @@ -6,14 +6,14 @@ import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; import '../timers.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; import '../utils.dart'; import 'transaction_base.dart'; class InviteClientTransaction extends TransactionBase { - InviteClientTransaction(UA ua, Transport transport, OutgoingRequest request, - EventManager eventHandlers) { + InviteClientTransaction(UA ua, SocketTransport transport, + OutgoingRequest request, EventManager eventHandlers) { id = 'z9hG4bK${(Math.random() * 10000000).floor()}'; this.ua = ua; this.transport = transport; diff --git a/lib/src/transactions/invite_server.dart b/lib/src/transactions/invite_server.dart index 8fdf65f0..6e92538c 100644 --- a/lib/src/transactions/invite_server.dart +++ b/lib/src/transactions/invite_server.dart @@ -4,13 +4,13 @@ import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; import '../timers.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; import 'transaction_base.dart'; class InviteServerTransaction extends TransactionBase { InviteServerTransaction( - UA ua, Transport? transport, IncomingRequest request) { + UA ua, SocketTransport? transport, IncomingRequest request) { id = request.via_branch; this.ua = ua; this.transport = transport; diff --git a/lib/src/transactions/non_invite_client.dart b/lib/src/transactions/non_invite_client.dart index 0ec01a45..3b2ba5fc 100644 --- a/lib/src/transactions/non_invite_client.dart +++ b/lib/src/transactions/non_invite_client.dart @@ -5,13 +5,13 @@ import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; import '../timers.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; import '../utils.dart'; import 'transaction_base.dart'; class NonInviteClientTransaction extends TransactionBase { - NonInviteClientTransaction(UA ua, Transport transport, + NonInviteClientTransaction(UA ua, SocketTransport transport, OutgoingRequest request, EventManager eventHandlers) { id = 'z9hG4bK${Math.random().floor()}'; this.ua = ua; diff --git a/lib/src/transactions/non_invite_server.dart b/lib/src/transactions/non_invite_server.dart index 3860cec6..fffc25a9 100644 --- a/lib/src/transactions/non_invite_server.dart +++ b/lib/src/transactions/non_invite_server.dart @@ -4,13 +4,13 @@ import 'package:sip_ua/src/sip_message.dart'; import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../timers.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; import 'transaction_base.dart'; class NonInviteServerTransaction extends TransactionBase { NonInviteServerTransaction( - UA ua, Transport? transport, IncomingRequest request) { + UA ua, SocketTransport? transport, IncomingRequest request) { id = request.via_branch; this.ua = ua; this.transport = transport; diff --git a/lib/src/transactions/transaction_base.dart b/lib/src/transactions/transaction_base.dart index f3da869d..38940087 100644 --- a/lib/src/transactions/transaction_base.dart +++ b/lib/src/transactions/transaction_base.dart @@ -1,6 +1,6 @@ import '../event_manager/event_manager.dart'; import '../sip_message.dart'; -import '../transport.dart'; +import '../socket_transport.dart'; import '../ua.dart'; enum TransactionState { @@ -17,7 +17,7 @@ enum TransactionState { abstract class TransactionBase extends EventManager { String? id; late UA ua; - Transport? transport; + SocketTransport? transport; TransactionState? state; IncomingMessage? last_response; dynamic request; diff --git a/lib/src/transport_constants.dart b/lib/src/transport_constants.dart new file mode 100644 index 00000000..a2d84e5c --- /dev/null +++ b/lib/src/transport_constants.dart @@ -0,0 +1,19 @@ +/** + * Constants + */ +class C { + // Transport status. + static const int STATUS_CONNECTED = 0; + static const int STATUS_CONNECTING = 1; + static const int STATUS_DISCONNECTED = 2; + + // Socket status. + static const int SOCKET_STATUS_READY = 0; + static const int SOCKET_STATUS_ERROR = 1; + + // Recovery options. + static const Map recovery_options = { + 'min_interval': 2, // minimum interval in seconds between recover attempts + 'max_interval': 30 // maximum interval in seconds between recover attempts + }; +} diff --git a/lib/src/transport_type.dart b/lib/src/transport_type.dart new file mode 100644 index 00000000..54cabf36 --- /dev/null +++ b/lib/src/transport_type.dart @@ -0,0 +1 @@ +enum TransportType { TCP, WS } diff --git a/lib/src/transports/socket_interface.dart b/lib/src/transports/socket_interface.dart new file mode 100644 index 00000000..aa74c2c6 --- /dev/null +++ b/lib/src/transports/socket_interface.dart @@ -0,0 +1,18 @@ +abstract class SIPUASocketInterface { + String? get url; + String? get sip_uri; + String get via_transport; + int? get weight; + set via_transport(String value); + + void Function()? onconnect; + void Function(SIPUASocketInterface socket, bool error, int? closeCode, + String? reason)? ondisconnect; + void Function(dynamic data)? ondata; + + void connect(); + void disconnect(); + bool send(dynamic message); + bool isConnected(); + bool isConnecting(); +} diff --git a/lib/src/transports/tcp_socket.dart b/lib/src/transports/tcp_socket.dart new file mode 100644 index 00000000..1820edf5 --- /dev/null +++ b/lib/src/transports/tcp_socket.dart @@ -0,0 +1,179 @@ +import 'package:sip_ua/sip_ua.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/tcp_socket_impl.dart'; +import '../grammar.dart'; +import '../logger.dart'; + +import 'websocket_dart_impl.dart' + if (dart.library.js) 'websocket_web_impl.dart'; + +class SIPUATcpSocket extends SIPUASocketInterface { + SIPUATcpSocket(String host, String port, + {required int messageDelay, + TcpSocketSettings? tcpSocketSettings, + int? weight}) + : _messageDelay = messageDelay { + logger.d('new() [host:$host:$port]'); + String transport_scheme = 'tcp'; + _weight = weight; + _host = host; + _port = port; + + _sip_uri = 'sip:$host:$port;transport=$transport_scheme'; + logger.d('TCPC SIP URI: $_sip_uri'); + _via_transport = transport_scheme.toUpperCase(); + _tcpSocketSettings = tcpSocketSettings ?? TcpSocketSettings(); + } + + final int _messageDelay; + + String? _host; + String? _port; + String? _sip_uri; + late String _via_transport; + final String _tcp_socket_protocol = 'sip'; + SIPUATcpSocketImpl? _tcpSocketImpl; + bool _closed = false; + bool _connected = false; + int? _weight; + int? status; + late TcpSocketSettings _tcpSocketSettings; + + @override + String get via_transport => _via_transport; + + @override + set via_transport(String value) { + _via_transport = value.toUpperCase(); + } + + @override + int? get weight => _weight; + + @override + String? get sip_uri => _sip_uri; + + String? get host => _host; + + String? get port => _port; + + @override + void connect() async { + logger.d('connect()'); + + if (_host == null) { + throw AssertionError('Invalid argument: _host'); + } + if (_port == null) { + throw AssertionError('Invalid argument: _port'); + } + + if (_tcpSocketImpl != null) { + disconnect(); + } + logger.d('connecting to TcpSocket $_host:$_port'); + try { + _tcpSocketImpl = SIPUATcpSocketImpl( + _messageDelay, _host ?? '0.0.0.0', _port ?? '5060'); + + _tcpSocketImpl!.onOpen = () { + _closed = false; + _connected = true; + logger.d('Tcp Socket is now connected?'); + _onOpen(); + }; + + _tcpSocketImpl!.onData = (dynamic data) { + _onMessage(data); + }; + + _tcpSocketImpl!.onClose = (int? closeCode, String? closeReason) { + logger.d('Closed [$closeCode, $closeReason]!'); + _connected = false; + _onClose(true, closeCode, closeReason); + }; + + _tcpSocketImpl!.connect( + protocols: [_tcp_socket_protocol], + tcpSocketSettings: _tcpSocketSettings); + } catch (e, s) { + logger.e(e.toString(), null, s); + _connected = false; + logger.e('TcpSocket error: $e'); + } + } + + @override + void disconnect() { + logger.d('disconnect()'); + if (_closed) return; + // Don't wait for the WebSocket 'close' event, do it now. + _closed = true; + _connected = false; + _onClose(true, 0, 'Client send disconnect'); + try { + if (_tcpSocketImpl != null) { + _tcpSocketImpl!.close(); + } + } catch (error) { + logger.e('close() | error closing the TcpSocket: $error'); + } + } + + @override + bool send(dynamic message) { + logger.d('send()'); + if (_closed) { + throw 'transport closed'; + } + try { + _tcpSocketImpl!.send(message); + return true; + } catch (error) { + logger.e('send() | error sending message: $error'); + throw error; + } + } + + @override + bool isConnected() { + return _connected; + } + + /** + * TcpSocket Event Handlers + */ + void _onOpen() { + logger.d('TcpSocket $_host:$port connected'); + onconnect!(); + } + + void _onClose(bool wasClean, int? code, String? reason) { + logger.d('TcpSocket $_host:$port closed'); + if (wasClean == false) { + logger.d('TcpSocket abrupt disconnection'); + } + ondisconnect!(this, !wasClean, code, reason); + } + + void _onMessage(dynamic data) { + logger.d('Received TcpSocket data'); + if (data != null) { + if (data.toString().trim().length > 0) { + ondata!(data); + } else { + logger.d('Received and ignored empty packet'); + } + } + } + + @override + bool isConnecting() { + // TODO: implement isConnecting + throw UnimplementedError(); + } + + @override + // TODO: implement url + String? get url => throw UnimplementedError(); +} diff --git a/lib/src/transports/tcp_socket_impl.dart b/lib/src/transports/tcp_socket_impl.dart new file mode 100644 index 00000000..d3e1de2e --- /dev/null +++ b/lib/src/transports/tcp_socket_impl.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:sip_ua/src/sip_ua_helper.dart'; +import '../logger.dart'; + +typedef OnMessageCallback = void Function(dynamic msg); +typedef OnCloseCallback = void Function(int? code, String? reason); +typedef OnOpenCallback = void Function(); + +class SIPUATcpSocketImpl { + SIPUATcpSocketImpl(this.messageDelay, this._host, this._port); + + final String _host; + final String _port; + + Socket? _socket; + OnOpenCallback? onOpen; + OnMessageCallback? onData; + OnCloseCallback? onClose; + final int messageDelay; + + void connect( + {Iterable? protocols, + required TcpSocketSettings tcpSocketSettings}) async { + handleQueue(); + logger.i('connect $_host:$_port'); + try { + if (tcpSocketSettings.allowBadCertificate) { + // /// Allow self-signed certificate, for test only. + // _socket = await _connectForBadCertificate(_url, tcpSocketSettings); + } else { + // used to have these + //protocols: protocols, headers: webSocketSettings.extraHeaders + _socket = await Socket.connect( + _host, + int.parse(_port), + ); + } + + onOpen?.call(); + + _socket!.listen((dynamic data) { + onData?.call(data); + }, onDone: () { + // onClose?.call(_socket!., _socket!.closeReason); + }); + } catch (e) { + onClose?.call(500, e.toString()); + } + } + + final StreamController queue = StreamController.broadcast(); + void handleQueue() async { + queue.stream.asyncMap((dynamic event) async { + await Future.delayed(Duration(milliseconds: messageDelay)); + return event; + }).listen((dynamic event) async { + _socket!.add(event.codeUnits); + logger.d('send: \n\n$event'); + }); + } + + void send(dynamic data) async { + if (_socket != null) { + queue.add(data); + } + } + + void close() { + _socket!.close(); + } +} diff --git a/lib/src/transports/websocket_interface.dart b/lib/src/transports/web_socket.dart similarity index 89% rename from lib/src/transports/websocket_interface.dart rename to lib/src/transports/web_socket.dart index a7c11a23..9fd511ff 100644 --- a/lib/src/transports/websocket_interface.dart +++ b/lib/src/transports/web_socket.dart @@ -1,17 +1,20 @@ import 'package:sip_ua/sip_ua.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; import '../grammar.dart'; import '../logger.dart'; -import '../socket.dart'; import 'websocket_dart_impl.dart' if (dart.library.js) 'websocket_web_impl.dart'; -class WebSocketInterface implements Socket { - WebSocketInterface(String url, - {required int messageDelay, WebSocketSettings? webSocketSettings}) +class SIPUAWebSocket extends SIPUASocketInterface { + SIPUAWebSocket(String url, + {required int messageDelay, + WebSocketSettings? webSocketSettings, + int? weight}) : _messageDelay = messageDelay { logger.d('new() [url:$url]'); _url = url; + _weight = weight; dynamic parsed_url = Grammar.parse(url, 'absoluteURI'); if (parsed_url == -1) { logger.e('invalid WebSocket URI: $url'); @@ -38,20 +41,13 @@ class WebSocketInterface implements Socket { String? _sip_uri; late String _via_transport; final String _websocket_protocol = 'sip'; - WebSocketImpl? _ws; + SIPUAWebSocketImpl? _ws; bool _closed = false; bool _connected = false; - int? weight; + int? _weight; int? status; late WebSocketSettings _webSocketSettings; - @override - void Function()? onconnect; - @override - void Function(WebSocketInterface socket, bool error, int? closeCode, - String? reason)? ondisconnect; - @override - void Function(dynamic data)? ondata; @override String get via_transport => _via_transport; @@ -63,6 +59,9 @@ class WebSocketInterface implements Socket { @override String? get sip_uri => _sip_uri; + @override + int? get weight => _weight; + @override String? get url => _url; @@ -86,7 +85,7 @@ class WebSocketInterface implements Socket { } logger.d('connecting to WebSocket $_url'); try { - _ws = WebSocketImpl(_url!, _messageDelay); + _ws = SIPUAWebSocketImpl(_url!, _messageDelay); _ws!.onOpen = () { _closed = false; @@ -109,7 +108,7 @@ class WebSocketInterface implements Socket { protocols: [_websocket_protocol], webSocketSettings: _webSocketSettings); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); _connected = false; logger.e('WebSocket $_url error: $e'); } @@ -147,10 +146,12 @@ class WebSocketInterface implements Socket { } } + @override bool isConnected() { return _connected; } + @override bool isConnecting() { return _ws != null && _ws!.isConnecting(); } diff --git a/lib/src/transports/websocket_dart_impl.dart b/lib/src/transports/websocket_dart_impl.dart index 5986aad4..6516515c 100644 --- a/lib/src/transports/websocket_dart_impl.dart +++ b/lib/src/transports/websocket_dart_impl.dart @@ -10,8 +10,8 @@ typedef OnMessageCallback = void Function(dynamic msg); typedef OnCloseCallback = void Function(int? code, String? reason); typedef OnOpenCallback = void Function(); -class WebSocketImpl { - WebSocketImpl(this._url, this.messageDelay); +class SIPUAWebSocketImpl { + SIPUAWebSocketImpl(this._url, this.messageDelay); final String _url; WebSocket? _socket; @@ -62,7 +62,7 @@ class WebSocketImpl { } void close() { - _socket!.close(); + if (_socket != null) _socket!.close(); } bool isConnecting() { diff --git a/lib/src/transports/websocket_web_impl.dart b/lib/src/transports/websocket_web_impl.dart index a9692581..f72ebe11 100644 --- a/lib/src/transports/websocket_web_impl.dart +++ b/lib/src/transports/websocket_web_impl.dart @@ -8,8 +8,8 @@ typedef OnMessageCallback = void Function(dynamic msg); typedef OnCloseCallback = void Function(int? code, String? reason); typedef OnOpenCallback = void Function(); -class WebSocketImpl { - WebSocketImpl(this._url, this.messageDelay); +class SIPUAWebSocketImpl { + SIPUAWebSocketImpl(this._url, this.messageDelay); final String _url; WebSocket? _socket; diff --git a/lib/src/ua.dart b/lib/src/ua.dart index ad467116..20a932dc 100644 --- a/lib/src/ua.dart +++ b/lib/src/ua.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:sip_ua/src/transport_type.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; import 'config.dart' as config; import 'config.dart'; import 'constants.dart' as DartSIP_C; @@ -17,6 +19,7 @@ import 'registrator.dart'; import 'rtc_session.dart'; import 'sanity_check.dart'; import 'sip_message.dart'; +import 'socket_transport.dart'; import 'subscriber.dart'; import 'timers.dart'; import 'transactions/invite_client.dart'; @@ -25,8 +28,7 @@ import 'transactions/non_invite_client.dart'; import 'transactions/non_invite_server.dart'; import 'transactions/transaction_base.dart'; import 'transactions/transactions.dart'; -import 'transport.dart'; -import 'transports/websocket_interface.dart'; +import 'transports/web_socket.dart'; import 'uri.dart'; import 'utils.dart' as Utils; @@ -114,11 +116,11 @@ class UA extends EventManager { final Set _applicants = {}; final Map _sessions = {}; - Transport? _transport; + SocketTransport? _socketTransport; Contact? _contact; int _status = C.STATUS_INIT; int? _error; - final TransactionBag _transactions = TransactionBag(); + late TransactionBag _transactions; // Custom UA empty object for high level use. final Map _data = {}; @@ -132,7 +134,7 @@ class UA extends EventManager { Settings get configuration => _configuration; - Transport? get transport => _transport; + SocketTransport? get socketTransport => _socketTransport; TransactionBag get transactions => _transactions; @@ -150,8 +152,10 @@ class UA extends EventManager { void start() { logger.d('start()'); + _transactions = TransactionBag(); + if (_status == C.STATUS_INIT) { - _transport!.connect(); + _socketTransport!.connect(); } else if (_status == C.STATUS_USER_CLOSED) { logger.d('restarting UA'); @@ -159,12 +163,12 @@ class UA extends EventManager { if (_closeTimer != null) { clearTimeout(_closeTimer); _closeTimer = null; - _transport!.disconnect(); + _socketTransport!.disconnect(); } // Reconnect. _status = C.STATUS_INIT; - _transport!.connect(); + _socketTransport!.connect(); } else if (_status == C.STATUS_READY) { logger.d('UA is in READY status, not restarted'); } else { @@ -232,7 +236,7 @@ class UA extends EventManager { * Connection state. */ bool isConnected() { - return _transport!.isConnected(); + return _socketTransport!.isConnected(); } /** @@ -331,7 +335,7 @@ class UA extends EventManager { rtcSession.terminate(); } } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); } } }); @@ -344,7 +348,7 @@ class UA extends EventManager { Subscriber subscriber = _subscribers[key]!; subscriber.terminate(null); } catch (e, s) { - logger.e(e.toString(), error: e, stackTrace: s); + logger.e(e.toString(), e, s); } } }); @@ -362,12 +366,12 @@ class UA extends EventManager { int num_transactions = _transactions.countTransactions(); if (num_transactions == 0 && num_sessions == 0) { - _transport!.disconnect(); + _socketTransport!.disconnect(); } else { _closeTimer = setTimeout(() { logger.i('Closing connection'); _closeTimer = null; - _transport!.disconnect(); + _socketTransport!.disconnect(); }, 2000); } } @@ -619,11 +623,11 @@ class UA extends EventManager { // Create the server transaction. if (method == SipMethod.INVITE) { /* eslint-disable no-*/ - InviteServerTransaction(this, _transport, request); + InviteServerTransaction(this, _socketTransport, request); /* eslint-enable no-*/ } else if (method != SipMethod.ACK && method != SipMethod.CANCEL) { /* eslint-disable no-*/ - NonInviteServerTransaction(this, _transport, request); + NonInviteServerTransaction(this, _socketTransport, request); /* eslint-enable no-*/ } @@ -835,30 +839,25 @@ class UA extends EventManager { .toString() .replaceAll(RegExp(r'sip:', caseSensitive: false), ''); - // Transport. + // Websockets Transport + try { - _transport = Transport(_configuration.sockets!, { + _socketTransport = SocketTransport(_configuration.sockets!, { // Recovery options. 'max_interval': _configuration.connection_recovery_max_interval, 'min_interval': _configuration.connection_recovery_min_interval }); // Transport event callbacks. - _transport!.onconnecting = onTransportConnecting; - _transport!.onconnect = onTransportConnect; - _transport!.ondisconnect = onTransportDisconnect; - _transport!.ondata = onTransportData; + _socketTransport!.onconnecting = onTransportConnecting; + _socketTransport!.onconnect = onTransportConnect; + _socketTransport!.ondisconnect = onTransportDisconnect; + _socketTransport!.ondata = onTransportData; } catch (e) { logger.e('Failed to _loadConfig: ${e.toString()}'); throw Exceptions.ConfigurationError('sockets', _configuration.sockets); } - String transport = 'ws'; - - if (_configuration.sockets!.isNotEmpty) { - transport = _configuration.sockets!.first.via_transport.toLowerCase(); - } - // Remove sockets instance from configuration object. // TODO(cloudwebrtc): need dispose?? _configuration.sockets = null; @@ -880,6 +879,8 @@ class UA extends EventManager { // User no_answer_timeout. _configuration.no_answer_timeout *= 1000; + String transport = _configuration.transportType?.name ?? 'WS'; + // Via Host. if (_configuration.contact_uri != null) { _configuration.via_host = _configuration.contact_uri.host; @@ -902,13 +903,13 @@ class UA extends EventManager { */ // Transport connecting event. - void onTransportConnecting(WebSocketInterface? socket, int? attempts) { + void onTransportConnecting(SIPUASocketInterface? socket, int? attempts) { logger.d('Transport connecting'); emit(EventSocketConnecting(socket: socket)); } // Transport connected event. - void onTransportConnect(Transport transport) { + void onTransportConnect(SocketTransport transport) { logger.d('Transport connected'); if (_status == C.STATUS_USER_CLOSED) { return; @@ -924,7 +925,7 @@ class UA extends EventManager { } // Transport disconnected event. - void onTransportDisconnect(WebSocketInterface? socket, ErrorCause cause) { + void onTransportDisconnect(SIPUASocketInterface? socket, ErrorCause cause) { // Run _onTransportError_ callback on every client transaction using _transport_. _transactions.removeAll().forEach((TransactionBase transaction) { transaction.onTransportError(); @@ -942,7 +943,7 @@ class UA extends EventManager { } // Transport data event. - void onTransportData(Transport transport, String messageData) { + void onTransportData(SocketTransport transport, String messageData) { IncomingMessage? message = Parser.parseMessage(messageData, this); if (message == null) { diff --git a/test/test_sip_ua.dart b/test/test_sip_ua.dart index eaa72871..17af0e99 100644 --- a/test/test_sip_ua.dart +++ b/test/test_sip_ua.dart @@ -2,7 +2,8 @@ import 'dart:async'; import 'package:sip_ua/src/config.dart' as config; import 'package:sip_ua/src/event_manager/event_manager.dart'; -import 'package:sip_ua/src/transports/websocket_interface.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/web_socket.dart'; import 'package:sip_ua/src/ua.dart'; import 'package:test/test.dart'; @@ -11,8 +12,8 @@ void main() { test(' WebSocket: EchoTest', () async { Completer completer = Completer(); config.Settings configuration = config.Settings(); - configuration.sockets = [ - WebSocketInterface('ws://127.0.0.1:5070/sip', messageDelay: 0) + configuration.sockets = [ + SIPUAWebSocket('ws://127.0.0.1:5070/sip', messageDelay: 0) ]; configuration.authorization_user = '100'; configuration.password = '100'; diff --git a/test/test_websocket.dart b/test/test_websocket.dart index 2d733aaf..81ac1d59 100644 --- a/test/test_websocket.dart +++ b/test/test_websocket.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'package:sip_ua/src/event_manager/events.dart'; -import 'package:sip_ua/src/transport.dart'; -import 'package:sip_ua/src/transports/websocket_interface.dart'; +import 'package:sip_ua/src/socket_transport.dart'; +import 'package:sip_ua/src/transports/socket_interface.dart'; +import 'package:sip_ua/src/transports/web_socket.dart'; import 'package:test/test.dart'; List testFunctions = [ @@ -28,8 +29,8 @@ List testFunctions = [ } }); - WebSocketInterface client = - WebSocketInterface('ws://127.0.0.1:4040/sip', messageDelay: 0); + SIPUAWebSocket client = + SIPUAWebSocket('ws://127.0.0.1:4040/sip', messageDelay: 0); expect(client.url, 'ws://127.0.0.1:4040/sip'); expect(client.via_transport, 'WS'); @@ -47,7 +48,7 @@ List testFunctions = [ client.disconnect(); completer.complete(); }; - client.ondisconnect = (WebSocketInterface socket, bool error, + client.ondisconnect = (SIPUASocketInterface socket, bool error, int? closeCode, String? reason) { print( 'ondisconnect => error $error [$closeCode] ${reason.toString()}'); @@ -76,26 +77,27 @@ List testFunctions = [ print(' An error occurred. $error'); } }); - WebSocketInterface socket = - WebSocketInterface('ws://127.0.0.1:4041/sip', messageDelay: 0); - Transport trasnport = Transport([socket]); + SIPUAWebSocket socket = + SIPUAWebSocket('ws://127.0.0.1:4041/sip', messageDelay: 0); + SocketTransport trasnport = + SocketTransport([socket]); - trasnport.onconnecting = (WebSocketInterface? socket, int? attempt) { + trasnport.onconnecting = (SIPUASocketInterface? socket, int? attempt) { expect(trasnport.isConnecting(), true); }; - trasnport.onconnect = (Transport socket) { + trasnport.onconnect = (SocketTransport socket) { expect(trasnport.isConnected(), true); trasnport.send('message'); }; - trasnport.ondata = (Transport transport, String messageData) { + trasnport.ondata = (SocketTransport transport, String messageData) { // expect(socket['message'], 'message'); trasnport.disconnect(); }; trasnport.ondisconnect = - (WebSocketInterface? socket, ErrorCause cause) { + (SIPUASocketInterface? socket, ErrorCause cause) { expect(trasnport.isConnected(), false); completer.complete(); }; From 73e205da9213a5e5e98faccbf3ce95f739160e8b Mon Sep 17 00:00:00 2001 From: Luan <72029952+luan-evangelista@users.noreply.github.com> Date: Wed, 8 May 2024 11:18:29 -0300 Subject: [PATCH 12/18] feat: Adding extraHeaders in RegisterParams, exposing existing functionality via UaSettings() (#432) --- lib/src/config.dart | 6 ++++++ lib/src/registrator.dart | 2 +- lib/src/sip_ua_helper.dart | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/config.dart b/lib/src/config.dart index a38804bb..15256a5d 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -39,6 +39,7 @@ class Settings { bool? register = true; int? register_expires = 600; dynamic registrar_server; + List? register_extra_headers; Map? register_extra_contact_uri_params; // Dtmf mode @@ -237,6 +238,11 @@ class Checks { dst!.registrar_server = parsed; } }, + 'register_extra_headers': (src, dst) { + var register_extra_headers = src?.register_extra_headers; + if (register_extra_headers == null) return; + dst?.register_extra_headers = register_extra_headers; + }, 'register_extra_contact_uri_params': (Settings src, Settings? dst) { Map? register_extra_contact_uri_params = src.register_extra_contact_uri_params; diff --git a/lib/src/registrator.dart b/lib/src/registrator.dart index 806bfbf7..4de8deb9 100644 --- a/lib/src/registrator.dart +++ b/lib/src/registrator.dart @@ -54,7 +54,7 @@ class Registrator { _contact += ';+sip.ice'; // Custom headers for REGISTER and un-REGISTER. - _extraHeaders = []; + _extraHeaders = ua.configuration.register_extra_headers; // Custom Contact header params for REGISTER and un-REGISTER. _extraContactParams = ''; diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index 3bd8feaf..a9f138c0 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -153,6 +153,7 @@ class SIPUAHelper extends EventManager { _settings.user_agent = uaSettings.userAgent ?? DartSIP_C.USER_AGENT; _settings.register = uaSettings.register; _settings.register_expires = uaSettings.register_expires; + _settings.register_extra_headers = uaSettings.registerParams.extraHeaders; _settings.register_extra_contact_uri_params = uaSettings.registerParams.extraContactUriParams; _settings.dtmf_mode = uaSettings.dtmfMode; @@ -683,6 +684,7 @@ class RegisterParams { /// Allow extra headers and Contact Params to be sent on REGISTER /// Mainly used for RFC8599 Support /// https://github.com/cloudwebrtc/dart-sip-ua/issues/89 + List extraHeaders = []; Map extraContactUriParams = {}; } From 05c2673a949f508f95fe1419a05fdfeab20c42b6 Mon Sep 17 00:00:00 2001 From: Paulo Albuquerque Date: Wed, 8 May 2024 11:21:01 -0300 Subject: [PATCH 13/18] add contact_uri as parameter (#426) Co-authored-by: Paulo Albuquerque --- example/lib/src/register.dart | 1 + lib/src/sip_ua_helper.dart | 2 ++ 2 files changed, 3 insertions(+) diff --git a/example/lib/src/register.dart b/example/lib/src/register.dart index 3081c0bb..9afd1a76 100644 --- a/example/lib/src/register.dart +++ b/example/lib/src/register.dart @@ -136,6 +136,7 @@ class _MyRegisterWidget extends State settings.displayName = _displayNameController.text; settings.userAgent = 'Dart SIP Client v1.0.0'; settings.dtmfMode = DtmfMode.RFC2833; + settings.contact_uri = 'sip:${_sipUriController.text}'; helper!.start(settings); } diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index a9f138c0..ac2a4de8 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -163,6 +163,7 @@ class SIPUAHelper extends EventManager { uaSettings.sessionTimersRefreshMethod; _settings.instance_id = uaSettings.instanceId; _settings.registrar_server = uaSettings.registrarServer; + _settings.contact_uri = uaSettings.contact_uri; try { _ua = UA(_settings); @@ -749,6 +750,7 @@ class UaSettings { String? displayName; String? instanceId; String? registrarServer; + String? contact_uri; TransportType? transportType; From 3342a4b91a4a2174524f3e239c931bb5377263fb Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 8 May 2024 22:41:46 +0800 Subject: [PATCH 14/18] Release/0.6.0 (#437) * fix log print. * bump version flutter-webrtc. * changlog. --- CHANGELOG.md | 14 ++++++++++++++ example/pubspec.yaml | 1 - lib/src/event_manager/event_manager.dart | 4 ++-- lib/src/rtc_session.dart | 7 ++++--- lib/src/sip_ua_helper.dart | 5 +++-- lib/src/socket_transport.dart | 3 +-- lib/src/transports/tcp_socket.dart | 2 +- lib/src/transports/web_socket.dart | 2 +- lib/src/ua.dart | 4 ++-- pubspec.yaml | 4 ++-- 10 files changed, 30 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f518fd6..ef8386ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog -------------------------------------------- +[0.6.0] - 2024.05.08 + +* Add status code to CallState on the progress event by @hiratake55 in https://github.com/flutter-webrtc/dart-sip-ua/pull/374 +* expose rtc_session to/from-tag by @ftsef in https://github.com/flutter-webrtc/dart-sip-ua/pull/378 +* #376: Added `_settings.session_timers_refresh_method` to UaSettings t… by @komaxx in https://github.com/flutter-webrtc/dart-sip-ua/pull/377 +* expose instance_id by @VictorUvarov in https://github.com/flutter-webrtc/dart-sip-ua/pull/395 +* Example app cleanup by @VictorUvarov in https://github.com/flutter-webrtc/dart-sip-ua/pull/394 +* fix: parse expires from string header. by @cloudwebrtc in https://github.com/flutter-webrtc/dart-sip-ua/pull/396 +* Add support for UAConfiguration registrar_server by @VictorUvarov in https://github.com/flutter-webrtc/dart-sip-ua/pull/397 +* Feature/update dependency to intl v19 by @komaxx in https://github.com/flutter-webrtc/dart-sip-ua/pull/414 +* Tcp socket implementation by @mikaelwills in https://github.com/flutter-webrtc/dart-sip-ua/pull/416 +* feat: Adding extraHeaders in RegisterParams, exposing existing functionality via UaSettings() by @luan-evangelista in https://github.com/flutter-webrtc/dart-sip-ua/pull/432 +* add contact_uri as parameter by @pjalbuquerque in https://github.com/flutter-webrtc/dart-sip-ua/pull/426 + [0.5.8] - 2023.05.11 * Allow await on helper start call (#365) * Adding params support for sendMessage (#366) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c93c5de2..748cafc6 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,7 +25,6 @@ dependencies: path: ../ shared_preferences: ^2.2.0 permission_handler: ^11.1.0 - flutter_webrtc: ^0.9.40 dev_dependencies: flutter_test: diff --git a/lib/src/event_manager/event_manager.dart b/lib/src/event_manager/event_manager.dart index 7fd8a9f6..aa84e7b0 100644 --- a/lib/src/event_manager/event_manager.dart +++ b/lib/src/event_manager/event_manager.dart @@ -69,7 +69,7 @@ class EventManager { targets.remove(listener); targets.add(listener); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); } } @@ -108,7 +108,7 @@ class EventManager { // logger.w("invoking $event on $target"); target(event); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); } } } diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 6ec5fa1c..1de029d6 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -693,7 +693,8 @@ class RTCSession extends EventManager implements Owner { if (_status == C.STATUS_TERMINATED) { return; } - logger.e('Failed to answer(): ${error.toString()}', error, s); + logger.e('Failed to answer(): ${error.toString()}', + error: error, stackTrace: s); } } @@ -2295,7 +2296,7 @@ class RTCSession extends EventManager implements Owner { request_sender.send(); } catch (error, s) { - logger.e(error.toString(), error, s); + logger.e(error.toString(), error: error, stackTrace: s); _failed('local', null, null, null, 500, DartSIP_C.CausesType.WEBRTC_ERROR, 'Can\'t create local SDP'); if (_status == C.STATUS_TERMINATED) { @@ -2561,7 +2562,7 @@ class RTCSession extends EventManager implements Owner { 'eventHandlers': handlers }); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); onFailed(); } } diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index ac2a4de8..f8fbaac0 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -105,7 +105,8 @@ class SIPUAHelper extends EventManager { _ua!.call(target, options); return true; } else { - logger.e('Not connected, you will need to register.', StackTraceNJ()); + logger.e('Not connected, you will need to register.', + stackTrace: StackTraceNJ()); } return false; } @@ -234,7 +235,7 @@ class SIPUAHelper extends EventManager { _ua!.start(); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); } } diff --git a/lib/src/socket_transport.dart b/lib/src/socket_transport.dart index b6321723..49385603 100644 --- a/lib/src/socket_transport.dart +++ b/lib/src/socket_transport.dart @@ -130,8 +130,7 @@ class SocketTransport { if (!isConnected()) { logger.e( 'unable to send message, transport is not connected. Current state is $status', - null, - StackTraceNJ()); + stackTrace: StackTraceNJ()); return false; } diff --git a/lib/src/transports/tcp_socket.dart b/lib/src/transports/tcp_socket.dart index 1820edf5..a786a6e6 100644 --- a/lib/src/transports/tcp_socket.dart +++ b/lib/src/transports/tcp_socket.dart @@ -97,7 +97,7 @@ class SIPUATcpSocket extends SIPUASocketInterface { protocols: [_tcp_socket_protocol], tcpSocketSettings: _tcpSocketSettings); } catch (e, s) { - logger.e(e.toString(), null, s); + logger.e(e.toString(), stackTrace: s); _connected = false; logger.e('TcpSocket error: $e'); } diff --git a/lib/src/transports/web_socket.dart b/lib/src/transports/web_socket.dart index 9fd511ff..66123a00 100644 --- a/lib/src/transports/web_socket.dart +++ b/lib/src/transports/web_socket.dart @@ -108,7 +108,7 @@ class SIPUAWebSocket extends SIPUASocketInterface { protocols: [_websocket_protocol], webSocketSettings: _webSocketSettings); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); _connected = false; logger.e('WebSocket $_url error: $e'); } diff --git a/lib/src/ua.dart b/lib/src/ua.dart index 20a932dc..9b213b32 100644 --- a/lib/src/ua.dart +++ b/lib/src/ua.dart @@ -335,7 +335,7 @@ class UA extends EventManager { rtcSession.terminate(); } } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); } } }); @@ -348,7 +348,7 @@ class UA extends EventManager { Subscriber subscriber = _subscribers[key]!; subscriber.terminate(null); } catch (e, s) { - logger.e(e.toString(), e, s); + logger.e(e.toString(), error: e, stackTrace: s); } } }); diff --git a/pubspec.yaml b/pubspec.yaml index 2fde5bca..2c33cb07 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sip_ua -version: 0.5.8 +version: 0.6.0 description: A SIP UA stack for Flutter/Dart, based on flutter-webrtc, support iOS/Android/Destkop/Web. homepage: https://github.com/cloudwebrtc/dart-sip-ua environment: @@ -9,7 +9,7 @@ environment: dependencies: collection: ^1.18.0 crypto: ^3.0.3 - flutter_webrtc: ^0.9.47 + flutter_webrtc: ^0.10.4 intl: ^0.19.0 logger: ^2.0.2+1 path: ^1.6.4 From 51412e7fc3ab2e2e57a21f8c6851636717a26852 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 8 May 2024 22:44:37 +0800 Subject: [PATCH 15/18] Update publish.yaml --- .github/workflows/publish.yaml | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 4ae46b26..5c9d8574 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -5,17 +5,30 @@ on: types: [published] jobs: - publish: - + build: runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - name: Install project dependencies + run: flutter pub get + - name: Dart Format Check + run: dart format lib/ test/ --set-exit-if-changed + - name: Import Sorter Check + run: flutter pub run import_sorter:main --no-comments --exit-if-changed + - name: Dart Analyze Check + run: flutter analyze + - name: Dart Test Check + run: flutter test + #- name: Check Publish Warnings + # run: dart pub publish --dry-run - name: Publish - uses: sakebook/actions-flutter-pub-publisher@v1.3.1 + uses: k-paxian/dart-package-publisher@v1.5.1 with: - credential: ${{ secrets.CREDENTIAL_JSON }} - flutter_package: true - skip_test: true - dry_run: false + credentialJson: ${{ secrets.CREDENTIAL_JSON }} + flutter: true + skipTests: true + force: true From 8d88300d34e28fe2f0813e5dd0c1a4b1aa10a764 Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Wed, 8 May 2024 22:48:00 +0800 Subject: [PATCH 16/18] Update pubspec.yaml --- pubspec.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2c33cb07..73eb5e6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,4 +22,5 @@ dependencies: dev_dependencies: lints: ^3.0.0 - test: ^1.6.7 + import_sorter: ^4.6.0 + test: any From b2fab26224828c01186f5fb394958ca677e30f28 Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Wed, 8 May 2024 22:49:58 +0800 Subject: [PATCH 17/18] run import_sorter. --- lib/src/event_manager/notifier_events.dart | 1 - lib/src/grammar_parser.dart | 3 ++- lib/src/rtc_session.dart | 2 ++ lib/src/sanity_check.dart | 2 +- lib/src/sip_ua_helper.dart | 2 +- lib/src/transactions/invite_client.dart | 2 +- lib/src/transactions/invite_server.dart | 2 +- lib/src/transactions/non_invite_client.dart | 2 +- lib/src/transactions/non_invite_server.dart | 2 +- test/test_classes.dart | 3 ++- test/test_digest_authentication.dart | 3 ++- test/test_normalize_target.dart | 3 ++- test/test_parser.dart | 3 ++- test/test_sip_message_parser.dart | 2 +- test/test_sip_ua.dart | 3 ++- test/test_websocket.dart | 3 ++- 16 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lib/src/event_manager/notifier_events.dart b/lib/src/event_manager/notifier_events.dart index ef6e3e09..945e735b 100644 --- a/lib/src/event_manager/notifier_events.dart +++ b/lib/src/event_manager/notifier_events.dart @@ -1,5 +1,4 @@ import 'package:sip_ua/src/sip_message.dart'; - import 'events.dart'; class EventTerminated extends EventType { diff --git a/lib/src/grammar_parser.dart b/lib/src/grammar_parser.dart index 6eee4599..47eaebb6 100644 --- a/lib/src/grammar_parser.dart +++ b/lib/src/grammar_parser.dart @@ -1,8 +1,9 @@ // This code was generated by a tool. // Processing tool available at https://github.com/mezoni/peg -import 'dart:core'; import 'dart:convert'; +import 'dart:core'; + import 'data.dart'; import 'name_addr_header.dart'; import 'uri.dart'; diff --git a/lib/src/rtc_session.dart b/lib/src/rtc_session.dart index 1de029d6..37ee0622 100644 --- a/lib/src/rtc_session.dart +++ b/lib/src/rtc_session.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:convert'; + import 'package:crypto/crypto.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; import 'package:sdp_transform/sdp_transform.dart'; + import 'package:sip_ua/sip_ua.dart'; import 'constants.dart' as DartSIP_C; import 'constants.dart'; diff --git a/lib/src/sanity_check.dart b/lib/src/sanity_check.dart index 112672f0..01d1884a 100644 --- a/lib/src/sanity_check.dart +++ b/lib/src/sanity_check.dart @@ -2,9 +2,9 @@ import 'constants.dart' as DartSIP_C; import 'constants.dart'; import 'logger.dart'; import 'sip_message.dart'; +import 'socket_transport.dart'; import 'transactions/invite_server.dart'; import 'transactions/non_invite_server.dart'; -import 'socket_transport.dart'; import 'ua.dart'; import 'utils.dart' as Utils; diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index f8fbaac0..2ec1e5fe 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:logger/logger.dart'; + import 'package:sip_ua/sip_ua.dart'; import 'package:sip_ua/src/map_helper.dart'; - import 'package:sip_ua/src/transports/socket_interface.dart'; import 'package:sip_ua/src/transports/tcp_socket.dart'; import 'config.dart'; diff --git a/lib/src/transactions/invite_client.dart b/lib/src/transactions/invite_client.dart index 7ee4d86e..7c1a93a6 100644 --- a/lib/src/transactions/invite_client.dart +++ b/lib/src/transactions/invite_client.dart @@ -5,8 +5,8 @@ import '../constants.dart'; import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; -import '../timers.dart'; import '../socket_transport.dart'; +import '../timers.dart'; import '../ua.dart'; import '../utils.dart'; import 'transaction_base.dart'; diff --git a/lib/src/transactions/invite_server.dart b/lib/src/transactions/invite_server.dart index 6e92538c..f42246b2 100644 --- a/lib/src/transactions/invite_server.dart +++ b/lib/src/transactions/invite_server.dart @@ -3,8 +3,8 @@ import 'dart:async'; import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; -import '../timers.dart'; import '../socket_transport.dart'; +import '../timers.dart'; import '../ua.dart'; import 'transaction_base.dart'; diff --git a/lib/src/transactions/non_invite_client.dart b/lib/src/transactions/non_invite_client.dart index 3b2ba5fc..ee725543 100644 --- a/lib/src/transactions/non_invite_client.dart +++ b/lib/src/transactions/non_invite_client.dart @@ -4,8 +4,8 @@ import '../event_manager/event_manager.dart'; import '../event_manager/internal_events.dart'; import '../logger.dart'; import '../sip_message.dart'; -import '../timers.dart'; import '../socket_transport.dart'; +import '../timers.dart'; import '../ua.dart'; import '../utils.dart'; import 'transaction_base.dart'; diff --git a/lib/src/transactions/non_invite_server.dart b/lib/src/transactions/non_invite_server.dart index fffc25a9..d801b8e3 100644 --- a/lib/src/transactions/non_invite_server.dart +++ b/lib/src/transactions/non_invite_server.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:sip_ua/src/sip_message.dart'; import '../event_manager/internal_events.dart'; import '../logger.dart'; -import '../timers.dart'; import '../socket_transport.dart'; +import '../timers.dart'; import '../ua.dart'; import 'transaction_base.dart'; diff --git a/test/test_classes.dart b/test/test_classes.dart index 9efe9e28..5ef70df4 100644 --- a/test/test_classes.dart +++ b/test/test_classes.dart @@ -1,6 +1,7 @@ +import 'package:test/test.dart'; + import 'package:sip_ua/src/name_addr_header.dart'; import 'package:sip_ua/src/uri.dart'; -import 'package:test/test.dart'; List testFunctions = [ () => test('Class: URI', () { diff --git a/test/test_digest_authentication.dart b/test/test_digest_authentication.dart index a77b269a..a7738209 100644 --- a/test/test_digest_authentication.dart +++ b/test/test_digest_authentication.dart @@ -1,6 +1,7 @@ +import 'package:test/test.dart'; + import 'package:sip_ua/src/constants.dart'; import 'package:sip_ua/src/digest_authentication.dart'; -import 'package:test/test.dart'; // Results of this tests originally obtained from RFC 2617 and: // 'https://pernau.at/kd/sipdigest.php' diff --git a/test/test_normalize_target.dart b/test/test_normalize_target.dart index 79ce6147..37f7f0ed 100644 --- a/test/test_normalize_target.dart +++ b/test/test_normalize_target.dart @@ -1,6 +1,7 @@ +import 'package:test/test.dart'; + import 'package:sip_ua/src/uri.dart'; import 'package:sip_ua/src/utils.dart' as Utils; -import 'package:test/test.dart'; List testFunctions = [ () => test('NormalizeTarget: valid targets', () { diff --git a/test/test_parser.dart b/test/test_parser.dart index 71e251f4..5409d874 100644 --- a/test/test_parser.dart +++ b/test/test_parser.dart @@ -1,8 +1,9 @@ +import 'package:test/test.dart'; + import 'package:sip_ua/src/data.dart'; import 'package:sip_ua/src/grammar.dart'; import 'package:sip_ua/src/name_addr_header.dart'; import 'package:sip_ua/src/uri.dart'; -import 'package:test/test.dart'; List testFunctions = [ () => test('Parser: Host => [ domain, ipv4, ipv6 ].', () { diff --git a/test/test_sip_message_parser.dart b/test/test_sip_message_parser.dart index 8a9369a1..4b4018cd 100644 --- a/test/test_sip_message_parser.dart +++ b/test/test_sip_message_parser.dart @@ -1,6 +1,6 @@ -import 'package:sip_ua/src/parser.dart'; import 'package:test/test.dart'; +import 'package:sip_ua/src/parser.dart'; import 'data/sip_message.dart'; List testFunctions = [ diff --git a/test/test_sip_ua.dart b/test/test_sip_ua.dart index 17af0e99..1b4c8118 100644 --- a/test/test_sip_ua.dart +++ b/test/test_sip_ua.dart @@ -1,11 +1,12 @@ import 'dart:async'; +import 'package:test/test.dart'; + import 'package:sip_ua/src/config.dart' as config; import 'package:sip_ua/src/event_manager/event_manager.dart'; import 'package:sip_ua/src/transports/socket_interface.dart'; import 'package:sip_ua/src/transports/web_socket.dart'; import 'package:sip_ua/src/ua.dart'; -import 'package:test/test.dart'; late UA ua; void main() { diff --git a/test/test_websocket.dart b/test/test_websocket.dart index 81ac1d59..cad2dda5 100644 --- a/test/test_websocket.dart +++ b/test/test_websocket.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'dart:io'; +import 'package:test/test.dart'; + import 'package:sip_ua/src/event_manager/events.dart'; import 'package:sip_ua/src/socket_transport.dart'; import 'package:sip_ua/src/transports/socket_interface.dart'; import 'package:sip_ua/src/transports/web_socket.dart'; -import 'package:test/test.dart'; List testFunctions = [ () => test(' WebSocket: EchoTest', () async { From 3f2be15f26fc1f3a00b503513a278fade6db725b Mon Sep 17 00:00:00 2001 From: cloudwebrtc Date: Wed, 8 May 2024 23:26:41 +0800 Subject: [PATCH 18/18] fix analyzer. --- analysis_options.yaml | 4 ++-- example/pubspec.yaml | 1 + lib/sip_ua.dart | 1 - lib/src/config.dart | 4 ++-- lib/src/logger.dart | 2 +- lib/src/sip_ua_helper.dart | 2 +- lib/src/transports/tcp_socket.dart | 4 ++-- lib/src/ua.dart | 3 ++- pubspec.yaml | 2 +- 9 files changed, 12 insertions(+), 11 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 6f71e31c..3a1f08c9 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -29,7 +29,6 @@ linter: - valid_regexps - void_checks # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - always_specify_types - annotate_overrides - prefer_single_quotes @@ -63,4 +62,5 @@ analyzer: exclude: - lib/src/grammar_parser.dart - - tool/* \ No newline at end of file + - tool/* + - test/* \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 748cafc6..5ac266a0 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: path: ../ shared_preferences: ^2.2.0 permission_handler: ^11.1.0 + flutter_webrtc: ^0.10.4 dev_dependencies: flutter_test: diff --git a/lib/sip_ua.dart b/lib/sip_ua.dart index 03d5fc00..64d80491 100644 --- a/lib/sip_ua.dart +++ b/lib/sip_ua.dart @@ -1,4 +1,3 @@ -/// only expose the bare minimum of internals required export 'src/enum_helper.dart'; export 'src/sip_ua_helper.dart'; export 'src/transport_type.dart'; diff --git a/lib/src/config.dart b/lib/src/config.dart index 15256a5d..88d0541a 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -238,8 +238,8 @@ class Checks { dst!.registrar_server = parsed; } }, - 'register_extra_headers': (src, dst) { - var register_extra_headers = src?.register_extra_headers; + 'register_extra_headers': (Settings src, Settings? dst) { + List? register_extra_headers = src.register_extra_headers; if (register_extra_headers == null) return; dst?.register_extra_headers = register_extra_headers; }, diff --git a/lib/src/logger.dart b/lib/src/logger.dart index d991b757..d8989695 100644 --- a/lib/src/logger.dart +++ b/lib/src/logger.dart @@ -28,7 +28,7 @@ class MyLogPrinter extends LogPrinter { MyLogPrinter(this.currentWorkingDirectory); static final Map levelColors = { - Level.verbose: AnsiColor.fg(AnsiColor.grey(0.5)), + Level.trace: AnsiColor.fg(AnsiColor.grey(0.5)), Level.debug: AnsiColor.none(), Level.info: AnsiColor.fg(12), Level.warning: AnsiColor.fg(208), diff --git a/lib/src/sip_ua_helper.dart b/lib/src/sip_ua_helper.dart index 2ec1e5fe..9b2b9005 100644 --- a/lib/src/sip_ua_helper.dart +++ b/lib/src/sip_ua_helper.dart @@ -686,7 +686,7 @@ class RegisterParams { /// Allow extra headers and Contact Params to be sent on REGISTER /// Mainly used for RFC8599 Support /// https://github.com/cloudwebrtc/dart-sip-ua/issues/89 - List extraHeaders = []; + List extraHeaders = []; Map extraContactUriParams = {}; } diff --git a/lib/src/transports/tcp_socket.dart b/lib/src/transports/tcp_socket.dart index a786a6e6..d172fcf8 100644 --- a/lib/src/transports/tcp_socket.dart +++ b/lib/src/transports/tcp_socket.dart @@ -169,11 +169,11 @@ class SIPUATcpSocket extends SIPUASocketInterface { @override bool isConnecting() { - // TODO: implement isConnecting + // TODO(cloudwebrtc): implement isConnecting throw UnimplementedError(); } @override - // TODO: implement url + // TODO(cloudwebrtc): implement url String? get url => throw UnimplementedError(); } diff --git a/lib/src/ua.dart b/lib/src/ua.dart index 9b213b32..36eddc94 100644 --- a/lib/src/ua.dart +++ b/lib/src/ua.dart @@ -660,7 +660,8 @@ class UA extends EventManager { return; } } else if (method == SipMethod.SUBSCRIBE) { - if (listeners['newSubscribe']?.length == 0) { + // ignore: collection_methods_unrelated_type + if (listeners['newSubscribe'] == null) { request.reply(405); return; diff --git a/pubspec.yaml b/pubspec.yaml index 73eb5e6a..ac0d57f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,6 @@ dependencies: dev_dependencies: - lints: ^3.0.0 import_sorter: ^4.6.0 + lints: ^3.0.0 test: any