Skip to content

Conversation

@ikq
Copy link

@ikq ikq commented May 20, 2021

I added Subscriber.js Notifier.js to JsSIP

I tried write them according JsSIP style and lint errors.
Please take a look and let me know what needs to be fixed.

I already see that *.d.ts files are missing.

The work still in progress: I want add to SUBSCRIBE Contact +sip.instance
as you do in REGISTER

@jmillan
Copy link
Member

jmillan commented May 21, 2021

Hi @ikq,

JsSIP code must have the same style in order to make it readable and maintainable. Comments before making a deeper review:

  • Debug line as the first method call in al public method implementations.
  • New line after return.
  • New line before and after call to super().
  • First conditional in a block starts in new line and last ends in a new line.
  • Conditional blocks always contain curly brackets.
  • Comments in the line above when possible.
  • Calls to debug have usually a new line above and a new line below.

Please take a certain file (ie: RTCSession.js) as a reference and try to stick to its style.

@jmillan
Copy link
Member

jmillan commented May 25, 2021

@ikq

Thanks for the cleanup, 👍 We'll review the PR as we can.

@jmillan
Copy link
Member

jmillan commented May 27, 2021

@ikq,

Cosmetic comment, we use camelCase in API method arguments (not in 100% of the cases, I know, but we will get there). Could you please make such change?

Copy link
Member

@jmillan jmillan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the first review.

Please go through all the comments and modify as needed. We'll go for sencond review afterwards.

@ikq
Copy link
Author

ikq commented Jun 5, 2021

I mostly fixed the issues.

[I thought about dialog:
if you write in C++/java style, then you could add an abstract class dialog, and invite-dialog, subscriber and notifier dialogs would be his descendants.
The abstract dialog class could be something like stripped version of Dialog.js without actual method implementations]

About dialog, seems:

dialog interface:

  • id (string)
  • state (number)
  • receiveRequest receive incoming request with the id

other functionality:

  • check incoming requests
  • keep set of parameters to create outgoing request.

It's already implemented in Subscriber/Notifier.
[Not exactly as in Dialog.js, but the same functionality]

@jmillan
Copy link
Member

jmillan commented Jun 8, 2021

Hi,

Super busy lately. Will come back to this when we have some time.

@jmillan
Copy link
Member

jmillan commented Jun 22, 2021

H @ikq,

Dialog should be agnostic to the method. Why can't current Dialog implementation be used here? It has basically the sendRequest() method that you can call, and it will call the receiveRequest() method on your Subscriber/Notifier implementation.

@ikq
Copy link
Author

ikq commented Jun 22, 2021

Hmm...
I checked the Dialog.js code again.
sendRequest and receiveRequest can be used.

I just did not consider this possibility before because I found INVITE/CANCEL/ACK specific code and properties in the Dialog (e.g. subscriber used different dialog states set).

@jmillan
Copy link
Member

jmillan commented Jun 22, 2021

There is INVITE related code in Dialog because a dialog for INVITE method needs to behave in certain way, different to generic dialogs, but that's an implementation detail. Other clases should be able to create dialogs the same way RTCSession does.

@ikq
Copy link
Author

ikq commented Jun 24, 2021

Hi Jose,
Now subscriber and notifier used Dialog.js
It passed my basic testing now.

I'll continue to test next week:

  • I cannot check subscriber authentication (my sip server don't request it).
  • subscriber with some production server.
  • subscriber with notifier.

@ikq
Copy link
Author

ikq commented Jul 7, 2021

Testing of the current version has been completed successfully.
(subscribe authentication was not checked)

@ikq
Copy link
Author

ikq commented Jul 10, 2021

subscribe authentication checked using special test.
IMHO: the subscriber/notifier is ready.

@ikq ikq requested a review from jmillan August 3, 2021 15:49
@ikq
Copy link
Author

ikq commented Aug 8, 2021

I see I should change debug/debugerror to logger...

@jmillan
Copy link
Member

jmillan commented Aug 18, 2021

Hi @ikq,

We are super busy lately. Please keep using your branch and update this PR whenever you find any issue/enhancement.

Also, tests would accelerate the adoption.

@ikq
Copy link
Author

ikq commented Aug 20, 2021

I already use this code for my work and so far no problem.

Two browsers test can be taken from:
https://github.com/ikq/subscribe_notify_test

@jmillan
Copy link
Member

jmillan commented Aug 23, 2021

@ikq, more than an external test I mean tests in JsSIP as part of this PR, testing the clases created here.

@N4COM
Copy link

N4COM commented Sep 22, 2021

Hi, is there some news? I'd like to test this functionality.

@ikq
Copy link
Author

ikq commented Sep 22, 2021

Hi, is there some news? I'd like to test this functionality.

IMHO it's stable code.
[At least we use subscriber in our production, currently no issues reported]

The subscriber/notifier test can taken from https://github.com/ikq/subscribe_notify_test

@markusatm
Copy link
Contributor

Hi, is there a specific reason why Subscriber and Notifier don't use the Contact header from the UA like RTCSession does? If the user provides a custom contact_uri in the UAConfiguration he would have to manually add it as a extra header to Subscriber and Notifier again, wouldn't he?

@N4COM
Copy link

N4COM commented Sep 22, 2021

Hi, is there some news? I'd like to test this functionality.

IMHO it's stable code.
[At least we use subscriber in our production, currently no issues reported]

The subscriber/notifier test can taken from https://github.com/ikq/subscribe_notify_test

I'd like to use the official repository, so I'd like if it will be merged ASAP

@ikq
Copy link
Author

ikq commented Sep 22, 2021

Hi, is there a specific reason why Subscriber and Notifier don't use the Contact header from the UA like RTCSession does? If the user provides a custom contact_uri in the UAConfiguration he would have to manually add it as a extra header to Subscriber and Notifier again, wouldn't he?

Yes, I'm not using the Configuration.contact_uri

In fact, I was in a hurry to do this work, and did not pay attention at all to that there is such a parameter in the configuration !

Let check, how the configuration used for INVITE:

UA.js:
   this._contact = {
      pub_gruu  : null,
      temp_gruu : null,
      uri       : this._configuration.contact_uri,

RTCSession.js:
  this._contact = this._ua.contact.toString({
      anonymous,
      outbound : true
    });

Thanks.
It can be improved (use of Configuration.contact_uri)

P.S.
Usage Configuration.contact_uri will modify default SUBSCRIBE contact - will be missed "+sip.instance" (that I add to default SUBSCRIBE Contact)
User still can set "+sip.instance" to SUBSCRIBE Contact via custom Contact header.

@mitre-mgarber
Copy link

@ikq if you don't mind external contributions i'd be willing to take a swing at writing unit tests for this project's test framework

@ikq
Copy link
Author

ikq commented Sep 22, 2021

if you don't mind external contributions i'd be willing to take a swing at writing unit tests for this project's test framework

Thank you, I will gladly accept your help.

I have provided an external test for 2 browsers: one is sending an SUBSCRIBE and the other is receiving it and sending a NOTIFY.

Seems in unit test should be simulated 2 instances of JsSIP one send/other receive.
[I don't know how it can be written.
Probably instead two JsSIP instances, can be used single that send the message itself ?]
;-)

@stefang42
Copy link
Contributor

Thanks.
It can be improved (use of Configuration.contact_uri)

P.S.
Usage Configuration.contact_uri will modify default SUBSCRIBE contact - will be missed "+sip.instance" (that I add to default SUBSCRIBE Contact)
User still can set "+sip.instance" to SUBSCRIBE Contact via custom Contact header.

@ikq Is there a specific reason to use Configuration.contact_uri? Why not just do it as in RTCSession, with this._ua.contact.toString? Then it would also use the gruu if one was provided. Or am I missing something?

@ikq
Copy link
Author

ikq commented Sep 23, 2021

Thank you markusatm for founding the problem.
Thank you stefang42, I use your fix to build Contact in subscriber and notifier.

@jmillan
Copy link
Member

jmillan commented Sep 24, 2021

Hi,

Yes, in order for this PR to be merged we need tests in JsSIP project.

You don't need two JsSIP instances, just one instance and you should inject a transport that you control (a FAKE transport) that you use to send JsSIP UA SUBSCRIBE|NOTIFY messages and receive and check what comes from JsSIP.

If you need some help please let us know.

@ikq
Copy link
Author

ikq commented Sep 25, 2021

Hi,
I added nodeunit test to test subscriber notifier communication.

  1. Implemented loopSocket that send message itself, but modified Call-ID in each leg.
    (without the modification method findDialog confuses SUBSCRIBE dialog with NOTIFY dialog)

  2. subscriber and notifier run in the same JsSIP instance and send/receive SIP messages.

The test SIP sequence is:

  • SUBSCRIBE
  • SUBSCRIBE OK
  • NOTIFY
  • NOTIFY OK
  • unSUBSCRIBE (with expires 0)
  • unSUBSCRIBE OK
  • final NOTIFY (with Subscription-State: terminated)
  • final NOTIFY OK.

The unit test work silently, but if uncomment line JsSIP.debug.enable('JsSIP:*');
can be seen complete SIP communication.

SUBSCRIBE and NOTIFY headers checking:
url, method, contact, subscribe accept, notify subscription-state, content-type and body.
Checked order of event sequence.

@orgads
Copy link
Contributor

orgads commented Dec 25, 2025

@ikq thanks for the patience. I'll try 🤞 to finish the review and accept it in the following weeks. Can I ask you to not include any change in dist/ folder nor in package.json and to sync the branch with master?

Done

@orgads
Copy link
Contributor

orgads commented Dec 25, 2025

Hi @jmillan. Thanks for the review.

I work with @ikq at Audiocodes, and he agreed to let me take over this change as he has no time to address all the comments.

I pushed a new rebased revision, hopefully addressing all your comments. Please review.

@jmillan
Copy link
Member

jmillan commented Dec 25, 2025

Thanks, I'll take some time on a further, and hopefully last review.

@jmillan
Copy link
Member

jmillan commented Jan 7, 2026

Currently UA fires newSubscribe with the incoming SUBSCRIBE request.

If SUBSCRIBE request does not have a Contact header, the request WONT be responded, hence breaking the SIP protocol. Contact header is verified in Notifier constructor which simply throws if it's missing. This kind of sanity checks should be done in Notifier.init_incoming(subscriber) which should reject the request or emit newSubscribe. This method can be a static method in Notifier.

Check a similar case in RtcSession: https://github.com/versatica/JsSIP/blob/master/lib/RTCSession.js#L3499

@jmillan
Copy link
Member

jmillan commented Jan 7, 2026

@orgads, there may be some duplicated suggestion on my review due to a problem between GH and my side, sorry for that. Please simply resolve those duplicated.

@orgads orgads force-pushed the subscribe_support branch 2 times, most recently from fb75081 to 9c09c0f Compare January 7, 2026 16:36
@orgads
Copy link
Contributor

orgads commented Jan 7, 2026

I think all the comments should be now covered.

@orgads
Copy link
Contributor

orgads commented Jan 7, 2026

(I can't Resolve comments since I'm not the owner 🙄)

@jmillan
Copy link
Member

jmillan commented Jan 7, 2026

(I can't Resolve comments since I'm not the owner 🙄)

I will, no worries.

src/Notifier.js Outdated
Comment on lines 394 to 412
logger.debug(`emit "terminated" code=${C.SUBSCRIPTION_EXPIRED}, send final notify=true`);
this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call this._terminateDialog(C.SUBSCRIPTION_EXPIRED) instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It passes true for sendFinalNotify, and also doesn't call this._dialog.terminate().

@jmillan
Copy link
Member

jmillan commented Jan 9, 2026

Hi @orgads, I'm doing some modernisation on the library and I've moved code to src/ rather than lib/. Please adapt the PR at your convenience.

I don't plan to do any other change affecting this PR.

@orgads orgads force-pushed the subscribe_support branch from 9c09c0f to ddfc457 Compare January 11, 2026 15:59
@orgads
Copy link
Contributor

orgads commented Jan 11, 2026

Hi @orgads, I'm doing some modernisation on the library and I've moved code to src/ rather than lib/. Please adapt the PR at your convenience.

I don't plan to do any other change affecting this PR.

Rebased.

src/Notifier.js Outdated
}

logger.debug(`emit "terminated" code=${C.SUBSCRIPTION_EXPIRED}, send final notify=true`);
this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no type definitions (.d.ts) for events in Notifier.d.ts. Can you please add them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why the last argument is needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a subscription expires, final NOTIFY should be sent.

The purpose of this flag is to allow the client to send final notify with custom body on subscription expiry and call terminate (without JsSip destroying the dialog before that).

An alternative suggestion I discussed with @ikq is to add a callback named fillFinalNotify or so that will be called inside terminateDialog, allowing to fill custom body.

What do you prefer (or just send an empty NOTIFY, not allowing customization)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then rather than doing:

this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);

And letting the user call this.terminate() I would:

// Emit expired rather than terminate.
// The user can call `terminate()` within `expired` event callback.
this.emit('expired');
// Call `terminateDialog()`. It won't have any effect if the user already called `terminate()`.
this._terminateDialog(C.SUBSCRIPTION_EXPIRED);

NOTE: _terminateDialog() should check this._terminated and return if set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding _terminateDialog, not exactly. terminate must set _state to TERMINATED, because it is sent to the subscriber in sendNotify. But it does check for this._dialog, which should be enough.

Also, instead of calling _terminateDialog, I call terminate, to ensure we send a final notify.

The rest is done.

{
logger.debug('enqueue subscribe');

this._queue.push({ body, headers: Utils.cloneArray(headers) });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please point me where in the standard is this behaviour defined? The fact that SUBSCRIBE requests should be queued until the dialog is created?

The only use casi I find is that we want to terminate the subscription before receiving the response to the initial SUBSCRIBE, but than can be achieved on accepted and other event handlers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done in order to support the case of multiple incoming SUBSCRIBE requests that are received before the other party responds. _dialog is initialized on response, and then we have to send an initial NOTIFY that matches the body of the subscribes.

So we need to store body and headers anyway. The queue is only needed to support multiple requests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, incoming SUBSCRIBE request will end up creating a Notifier, not a Subscriber. Subscriber is only created locally. Right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I misunderstood the issue. If I understand correctly, it is for repeated calls to subscribe before the other party replies with 200 OK. So the first call sends SUBSCRIBE, then the next call should send an in-dialog SUBSCRIBE, but there is no dialog yet. So we need to wait for 200 OK, and then we send all the pending SUBSCRIBEs.

src/Notifier.js Outdated
Comment on lines 398 to 412
if (!this._dialog)
{
return;
}

logger.debug(`emit "terminated" code=${C.SUBSCRIPTION_EXPIRED}, send final notify=true`);
this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think this should call this._terminateDialog( C.SUBSCRIPTION_EXPIRED). I see no reason not to do it. Am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@orgads orgads force-pushed the subscribe_support branch from ddfc457 to c20b660 Compare January 12, 2026 13:52
Copy link
Contributor

@orgads orgads left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 comments still pending. I'll consult with @ikq.

@orgads orgads force-pushed the subscribe_support branch 2 times, most recently from 273076d to 60a678e Compare January 12, 2026 15:44
src/Notifier.js Outdated
return C;
}

get C()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one after constructor please:

  • first static methods.
  • second constructor.
  • Then all other methods.
    • public firsts.
    • private later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
logger.debug('enqueue subscribe');

this._queue.push({ body, headers: Utils.cloneArray(headers) });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, incoming SUBSCRIBE request will end up creating a Notifier, not a Subscriber. Subscriber is only created locally. Right?

src/Notifier.js Outdated
}

logger.debug(`emit "terminated" code=${C.SUBSCRIPTION_EXPIRED}, send final notify=true`);
this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then rather than doing:

this.emit('terminated', C.SUBSCRIPTION_EXPIRED, true);

And letting the user call this.terminate() I would:

// Emit expired rather than terminate.
// The user can call `terminate()` within `expired` event callback.
this.emit('expired');
// Call `terminateDialog()`. It won't have any effect if the user already called `terminate()`.
this._terminateDialog(C.SUBSCRIPTION_EXPIRED);

NOTE: _terminateDialog() should check this._terminated and return if set.


if (parsed === -1)
{
throw new TypeError('eventName - wrong format');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new TypeError('eventName - wrong format');
throw new TypeError('Missing Event header field');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

_terminateDialog(terminationCode, reason = undefined, retryAfter = undefined)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please move this after terminate() definition?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/Notifier.js Outdated

static init_incoming(request, callback)
{
if (request.method !== JsSIP_C.SUBSCRIBE || !request.hasHeader('contact'))
Copy link
Member

@jmillan jmillan Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event header field should also be verified here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@orgads orgads force-pushed the subscribe_support branch 2 times, most recently from c5160d4 to 6c1cfd7 Compare January 12, 2026 17:18
@orgads orgads force-pushed the subscribe_support branch from 6c1cfd7 to faff4fe Compare January 12, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.