Call transfer and multiple calls
in WebRTC with Janus
Lorenzo Miniero
@elminiero
OpenSIPS Summit
September 28th 2022,
Who am I?
Lorenzo Miniero
• Ph.D @ UniNA
• Chairman @ Meetecho
• Main author of Janus
Contacts and info
• lorenzo@meetecho.com
• https://twitter.com/elminiero
• https://www.slideshare.net/LorenzoMiniero
• https://lminiero.bandcamp.com
Just a few words on Meetecho
• Co-founded in 2009 as an academic spin-off
• University research efforts brought to the market
• Completely independent from the University
• Focus on real-time multimedia applications
• Strong perspective on standardization and open source
• Several activities
• Consulting services
• Commercial support and Janus licenses
• Streaming of live events (IETF, ACM, etc.)
• Proudly brewed in sunny Napoli(*), Italy
((*)
I’m legally obliged to show this)
Intro on the Janus SIP plugin features
https://www.youtube.com/watch?v=beHHL0Ew5xY
A quick reminder on what Janus is!
Janus
General purpose, open source WebRTC server
• https://github.com/meetecho/janus-gateway
• Demos and documentation: https://janus.conf.meetecho.com
• Community: https://groups.google.com/forum/#!forum/meetecho-janus
Extensible Architecture and API
Extensible Architecture and API
The SIP plugin in a nutshell
Works great with OpenSIPS!
An endpoint of behalf of WebRTC users
• Janus SIP plugin acts as a SIP endpoint, not a server/trunk!
• SIP stack implemented with Sofia-SIP
• WebRTC users only see the Janus API (JSON)
• No transcoding, media is only relayed
• Built-in recording (separate media legs)
• Simplifies life for web developers
• No need to worry about a SIP stack (only SIP URIs)
• Basic methods/events to handle calls (call, answer, hangup, message, etc.)
• Allows SIP headers injection in many requests
An endpoint of behalf of WebRTC users
• Janus SIP plugin acts as a SIP endpoint, not a server/trunk!
• SIP stack implemented with Sofia-SIP
• WebRTC users only see the Janus API (JSON)
• No transcoding, media is only relayed
• Built-in recording (separate media legs)
• Simplifies life for web developers
• No need to worry about a SIP stack (only SIP URIs)
• Basic methods/events to handle calls (call, answer, hangup, message, etc.)
• Allows SIP headers injection in many requests
What about call transfers?
• Since we first released Janus, this was asked for a few times
• e.g., https://github.com/meetecho/janus-gateway/issues/978
• Sofia SIP supports them, but additional work needed in Janus
• Support for generic SUBSCRIBE/NOTIFY
• Support for multiple calls at the same time
• Support for actual transfer signalling
• A Colombian company sponsored the development of them all!
• https://github.com/meetecho/janus-gateway/pull/1768 (merged)
• https://github.com/meetecho/janus-gateway/pull/1772 (merged)
• https://github.com/meetecho/janus-gateway/pull/1815 (merged)
What about call transfers?
• Since we first released Janus, this was asked for a few times
• e.g., https://github.com/meetecho/janus-gateway/issues/978
• Sofia SIP supports them, but additional work needed in Janus
• Support for generic SUBSCRIBE/NOTIFY
• Support for multiple calls at the same time
• Support for actual transfer signalling
• A Colombian company sponsored the development of them all!
• https://github.com/meetecho/janus-gateway/pull/1768 (merged)
• https://github.com/meetecho/janus-gateway/pull/1772 (merged)
• https://github.com/meetecho/janus-gateway/pull/1815 (merged)
What about call transfers?
• Since we first released Janus, this was asked for a few times
• e.g., https://github.com/meetecho/janus-gateway/issues/978
• Sofia SIP supports them, but additional work needed in Janus
• Support for generic SUBSCRIBE/NOTIFY
• Support for multiple calls at the same time
• Support for actual transfer signalling
• A Colombian company sponsored the development of them all!
• https://github.com/meetecho/janus-gateway/pull/1768 (merged)
• https://github.com/meetecho/janus-gateway/pull/1772 (merged)
• https://github.com/meetecho/janus-gateway/pull/1815 (merged)
1. Support for generic SUBSCRIBE/NOTIFY
• As discussed, a requirement for call transfers
• Transferor is notified about progress via NOTIFY (message/sipfrag)
• We started working on that first
• Feature entirely missing in Janus SIP plugin until then!
• Generic support opened door for other features (e.g., presence)
• Web application can send subscribe request and get notify events
• In case of call transfers, though, subscription done automatically
• Sofia SIP stack does that on its own when sending REFER
1. Support for generic SUBSCRIBE/NOTIFY
• As discussed, a requirement for call transfers
• Transferor is notified about progress via NOTIFY (message/sipfrag)
• We started working on that first
• Feature entirely missing in Janus SIP plugin until then!
• Generic support opened door for other features (e.g., presence)
• Web application can send subscribe request and get notify events
• In case of call transfers, though, subscription done automatically
• Sofia SIP stack does that on its own when sending REFER
1. Support for generic SUBSCRIBE/NOTIFY
• As discussed, a requirement for call transfers
• Transferor is notified about progress via NOTIFY (message/sipfrag)
• We started working on that first
• Feature entirely missing in Janus SIP plugin until then!
• Generic support opened door for other features (e.g., presence)
• Web application can send subscribe request and get notify events
• In case of call transfers, though, subscription done automatically
• Sofia SIP stack does that on its own when sending REFER
1. Support for generic SUBSCRIBE/NOTIFY
2. Support for multiple calls at the same time
• Another requirement for call transfers
• Endpoints may need to be in two calls at the same time, before completion
• Harder to implement due to how the SIP plugin worked
• In Janus, 1 handle = 1 PeerConnection (WebRTC media session)
• In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time
• Other calls refused with a 486, in case user is in a call already
• Ended up implementing generic mechanism for “multiple lines”
• User registers SIP account only on “main” handle (new SIP stack)
• User can add more lines via “helper” handles (SIP stack shared with “main”)
• In case “main” is in a call, additional calls can be handled on “helpers”
2. Support for multiple calls at the same time
• Another requirement for call transfers
• Endpoints may need to be in two calls at the same time, before completion
• Harder to implement due to how the SIP plugin worked
• In Janus, 1 handle = 1 PeerConnection (WebRTC media session)
• In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time
• Other calls refused with a 486, in case user is in a call already
• Ended up implementing generic mechanism for “multiple lines”
• User registers SIP account only on “main” handle (new SIP stack)
• User can add more lines via “helper” handles (SIP stack shared with “main”)
• In case “main” is in a call, additional calls can be handled on “helpers”
2. Support for multiple calls at the same time
• Another requirement for call transfers
• Endpoints may need to be in two calls at the same time, before completion
• Harder to implement due to how the SIP plugin worked
• In Janus, 1 handle = 1 PeerConnection (WebRTC media session)
• In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time
• Other calls refused with a 486, in case user is in a call already
• Ended up implementing generic mechanism for “multiple lines”
• User registers SIP account only on “main” handle (new SIP stack)
• User can add more lines via “helper” handles (SIP stack shared with “main”)
• In case “main” is in a call, additional calls can be handled on “helpers”
2. Support for multiple calls at the same time
2. Support for multiple calls at the same time
3. Support for actual transfer signalling
• Final step in development
• Based on new changes to support subscriptions and multiple lines
• Implemented as an orchestration of required interactions
• Tried to simplify the process on the web side (still no SIP there)
• New transfer request for both blind and attended transfers
• Mostly differ on whether we ask to replace a call or not
• For blind transfers, no Call-ID is provided
• For attended transfers, it is (and transferee is notified)
• Janus SIP plugin code tries to handle the differences automatically
3. Support for actual transfer signalling
• Final step in development
• Based on new changes to support subscriptions and multiple lines
• Implemented as an orchestration of required interactions
• Tried to simplify the process on the web side (still no SIP there)
• New transfer request for both blind and attended transfers
• Mostly differ on whether we ask to replace a call or not
• For blind transfers, no Call-ID is provided
• For attended transfers, it is (and transferee is notified)
• Janus SIP plugin code tries to handle the differences automatically
3. Support for actual transfer signalling
• Final step in development
• Based on new changes to support subscriptions and multiple lines
• Implemented as an orchestration of required interactions
• Tried to simplify the process on the web side (still no SIP there)
• New transfer request for both blind and attended transfers
• Mostly differ on whether we ask to replace a call or not
• For blind transfers, no Call-ID is provided
• For attended transfers, it is (and transferee is notified)
• Janus SIP plugin code tries to handle the differences automatically
3. Support for actual transfer signalling
Demoed this in an OpenSIPS workshop
Alice in a call with Bob...
Alice decides to transfer the call...
Alice refers Bob to Carol
Bob is notified about the transfer...
Carol receives the transfered call
A quick look under the hood (Janus API)
Alice Bob Carol
Session ID 2882853027068154 5709081044242221 8512736684868091
Main handle 704123326479426 7513373295190055 2834358747375312
Helper handle 7228699494761220 6823091173814012 4398393772247705
Table: Map of sessions and handles in the transfer examples
A quick look under the hood (Janus API)
{
"janus": "message",
"session_id": 2882853027068154,
"handle_id": 704123326479426,
"transaction": "EocmzzHbvyok",
"body": {
"request": "transfer",
"uri": "sip:carol@192.168.1.110"
}
}
A quick look under the hood (Janus API)
{
"janus": "event",
"session_id": 2882853027068154,
"transaction": "EocmzzHbvyok",
"sender": 704123326479426,
"opaque_id": "siptest-7LqZsog4X0MG",
"plugindata": {
"plugin": "janus.plugin.sip",
"data": {
"sip": "event",
"result": {
"event": "transferring"
},
"call_id": "YOO9sE77w3RqNFTPK18C6sN"
}
}
}
A quick look under the hood (Janus API)
{
"janus": "event",
"session_id": 5709081044242221,
"sender": 7513373295190055,
"opaque_id": "siptest-XHWhFZWbcjjG",
"plugindata": {
"plugin": "janus.plugin.sip",
"data": {
"sip": "event",
"result": {
"event": "transfer",
"refer_id": 991347953, <--- IMPORTANT!
"refer_to": "sip:carol@192.168.1.110",
"referred_by": "<sip:alice@192.168.1.110:36091>"
}
}
}
}
A quick look under the hood (Janus API)
{
"janus": "message",
"session_id": 5709081044242221,
"handle_id": 6823091173814012,
"transaction": "J1ndk8jz1yuE",
"body": {
"request": "call",
"uri": "sip:carol@192.168.1.110",
"refer_id": 991347953
},
"jsep":{
// WebRTC SDP stuff
}
}
A quick look under the hood (Janus API)
{
"janus": "event",
"session_id": 8512736684868091,
"sender": 4398393772247705,
"opaque_id": "siptest-748ihwAPS7Lw",
"plugindata": {
"plugin": "janus.plugin.sip",
"data": {
"sip": "event",
"call_id": "LgvQ1kV2MHluoXlJBe9MQs3",
"result": {
"event": "incomingcall",
"username": "sip:bob@192.168.1.110",
"callee": "sip:carol@192.168.1.110",
NOTICE ----> "referred_by": "<sip:alice@192.168.1.110:36091>"
}
}
},
A quick look under the hood (Janus API)
{
"janus": "event",
"session_id": 2882853027068154,
"transaction": "d7LoWBpinfTP",
"sender": 704123326479426,
"opaque_id": "siptest-7LqZsog4X0MG",
"plugindata": {
"plugin": "janus.plugin.sip",
"data": {
"sip": "event",
"result": {
"event": "notify",
"notify": "refer",
"substate": "active",
"content-type": "message/sipfrag",
"content": "SIP/2.0 180 Ringing"
}
}
A quick look under the hood (Janus API)
{
"janus": "event",
"session_id": 2882853027068154,
"transaction": "d7LoWBpinfTP",
"sender": 704123326479426,
"opaque_id": "siptest-7LqZsog4X0MG",
"plugindata": {
"plugin": "janus.plugin.sip",
"data": {
"sip": "event",
"result": {
"event": "notify",
"notify": "refer",
"substate": "active",
"content-type": "message/sipfrag",
"content": "SIP/2.0 200 OK"
}
}
Thanks! Questions? Comments?
Get in touch!
• https://twitter.com/elminiero
• https://twitter.com/meetecho
• http://www.meetecho.com

SIP transfer with Janus/WebRTC @ OpenSIPS 2022

  • 1.
    Call transfer andmultiple calls in WebRTC with Janus Lorenzo Miniero @elminiero OpenSIPS Summit September 28th 2022,
  • 2.
    Who am I? LorenzoMiniero • Ph.D @ UniNA • Chairman @ Meetecho • Main author of Janus Contacts and info • lorenzo@meetecho.com • https://twitter.com/elminiero • https://www.slideshare.net/LorenzoMiniero • https://lminiero.bandcamp.com
  • 3.
    Just a fewwords on Meetecho • Co-founded in 2009 as an academic spin-off • University research efforts brought to the market • Completely independent from the University • Focus on real-time multimedia applications • Strong perspective on standardization and open source • Several activities • Consulting services • Commercial support and Janus licenses • Streaming of live events (IETF, ACM, etc.) • Proudly brewed in sunny Napoli(*), Italy
  • 4.
  • 5.
    Intro on theJanus SIP plugin features https://www.youtube.com/watch?v=beHHL0Ew5xY
  • 6.
    A quick reminderon what Janus is! Janus General purpose, open source WebRTC server • https://github.com/meetecho/janus-gateway • Demos and documentation: https://janus.conf.meetecho.com • Community: https://groups.google.com/forum/#!forum/meetecho-janus
  • 7.
  • 8.
  • 9.
    The SIP pluginin a nutshell
  • 10.
  • 11.
    An endpoint ofbehalf of WebRTC users • Janus SIP plugin acts as a SIP endpoint, not a server/trunk! • SIP stack implemented with Sofia-SIP • WebRTC users only see the Janus API (JSON) • No transcoding, media is only relayed • Built-in recording (separate media legs) • Simplifies life for web developers • No need to worry about a SIP stack (only SIP URIs) • Basic methods/events to handle calls (call, answer, hangup, message, etc.) • Allows SIP headers injection in many requests
  • 12.
    An endpoint ofbehalf of WebRTC users • Janus SIP plugin acts as a SIP endpoint, not a server/trunk! • SIP stack implemented with Sofia-SIP • WebRTC users only see the Janus API (JSON) • No transcoding, media is only relayed • Built-in recording (separate media legs) • Simplifies life for web developers • No need to worry about a SIP stack (only SIP URIs) • Basic methods/events to handle calls (call, answer, hangup, message, etc.) • Allows SIP headers injection in many requests
  • 13.
    What about calltransfers? • Since we first released Janus, this was asked for a few times • e.g., https://github.com/meetecho/janus-gateway/issues/978 • Sofia SIP supports them, but additional work needed in Janus • Support for generic SUBSCRIBE/NOTIFY • Support for multiple calls at the same time • Support for actual transfer signalling • A Colombian company sponsored the development of them all! • https://github.com/meetecho/janus-gateway/pull/1768 (merged) • https://github.com/meetecho/janus-gateway/pull/1772 (merged) • https://github.com/meetecho/janus-gateway/pull/1815 (merged)
  • 14.
    What about calltransfers? • Since we first released Janus, this was asked for a few times • e.g., https://github.com/meetecho/janus-gateway/issues/978 • Sofia SIP supports them, but additional work needed in Janus • Support for generic SUBSCRIBE/NOTIFY • Support for multiple calls at the same time • Support for actual transfer signalling • A Colombian company sponsored the development of them all! • https://github.com/meetecho/janus-gateway/pull/1768 (merged) • https://github.com/meetecho/janus-gateway/pull/1772 (merged) • https://github.com/meetecho/janus-gateway/pull/1815 (merged)
  • 15.
    What about calltransfers? • Since we first released Janus, this was asked for a few times • e.g., https://github.com/meetecho/janus-gateway/issues/978 • Sofia SIP supports them, but additional work needed in Janus • Support for generic SUBSCRIBE/NOTIFY • Support for multiple calls at the same time • Support for actual transfer signalling • A Colombian company sponsored the development of them all! • https://github.com/meetecho/janus-gateway/pull/1768 (merged) • https://github.com/meetecho/janus-gateway/pull/1772 (merged) • https://github.com/meetecho/janus-gateway/pull/1815 (merged)
  • 16.
    1. Support forgeneric SUBSCRIBE/NOTIFY • As discussed, a requirement for call transfers • Transferor is notified about progress via NOTIFY (message/sipfrag) • We started working on that first • Feature entirely missing in Janus SIP plugin until then! • Generic support opened door for other features (e.g., presence) • Web application can send subscribe request and get notify events • In case of call transfers, though, subscription done automatically • Sofia SIP stack does that on its own when sending REFER
  • 17.
    1. Support forgeneric SUBSCRIBE/NOTIFY • As discussed, a requirement for call transfers • Transferor is notified about progress via NOTIFY (message/sipfrag) • We started working on that first • Feature entirely missing in Janus SIP plugin until then! • Generic support opened door for other features (e.g., presence) • Web application can send subscribe request and get notify events • In case of call transfers, though, subscription done automatically • Sofia SIP stack does that on its own when sending REFER
  • 18.
    1. Support forgeneric SUBSCRIBE/NOTIFY • As discussed, a requirement for call transfers • Transferor is notified about progress via NOTIFY (message/sipfrag) • We started working on that first • Feature entirely missing in Janus SIP plugin until then! • Generic support opened door for other features (e.g., presence) • Web application can send subscribe request and get notify events • In case of call transfers, though, subscription done automatically • Sofia SIP stack does that on its own when sending REFER
  • 19.
    1. Support forgeneric SUBSCRIBE/NOTIFY
  • 20.
    2. Support formultiple calls at the same time • Another requirement for call transfers • Endpoints may need to be in two calls at the same time, before completion • Harder to implement due to how the SIP plugin worked • In Janus, 1 handle = 1 PeerConnection (WebRTC media session) • In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time • Other calls refused with a 486, in case user is in a call already • Ended up implementing generic mechanism for “multiple lines” • User registers SIP account only on “main” handle (new SIP stack) • User can add more lines via “helper” handles (SIP stack shared with “main”) • In case “main” is in a call, additional calls can be handled on “helpers”
  • 21.
    2. Support formultiple calls at the same time • Another requirement for call transfers • Endpoints may need to be in two calls at the same time, before completion • Harder to implement due to how the SIP plugin worked • In Janus, 1 handle = 1 PeerConnection (WebRTC media session) • In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time • Other calls refused with a 486, in case user is in a call already • Ended up implementing generic mechanism for “multiple lines” • User registers SIP account only on “main” handle (new SIP stack) • User can add more lines via “helper” handles (SIP stack shared with “main”) • In case “main” is in a call, additional calls can be handled on “helpers”
  • 22.
    2. Support formultiple calls at the same time • Another requirement for call transfers • Endpoints may need to be in two calls at the same time, before completion • Harder to implement due to how the SIP plugin worked • In Janus, 1 handle = 1 PeerConnection (WebRTC media session) • In SIP plugin, 1 handle = 1 SIP endpoint −→ max 1 SIP call at a time • Other calls refused with a 486, in case user is in a call already • Ended up implementing generic mechanism for “multiple lines” • User registers SIP account only on “main” handle (new SIP stack) • User can add more lines via “helper” handles (SIP stack shared with “main”) • In case “main” is in a call, additional calls can be handled on “helpers”
  • 23.
    2. Support formultiple calls at the same time
  • 24.
    2. Support formultiple calls at the same time
  • 25.
    3. Support foractual transfer signalling • Final step in development • Based on new changes to support subscriptions and multiple lines • Implemented as an orchestration of required interactions • Tried to simplify the process on the web side (still no SIP there) • New transfer request for both blind and attended transfers • Mostly differ on whether we ask to replace a call or not • For blind transfers, no Call-ID is provided • For attended transfers, it is (and transferee is notified) • Janus SIP plugin code tries to handle the differences automatically
  • 26.
    3. Support foractual transfer signalling • Final step in development • Based on new changes to support subscriptions and multiple lines • Implemented as an orchestration of required interactions • Tried to simplify the process on the web side (still no SIP there) • New transfer request for both blind and attended transfers • Mostly differ on whether we ask to replace a call or not • For blind transfers, no Call-ID is provided • For attended transfers, it is (and transferee is notified) • Janus SIP plugin code tries to handle the differences automatically
  • 27.
    3. Support foractual transfer signalling • Final step in development • Based on new changes to support subscriptions and multiple lines • Implemented as an orchestration of required interactions • Tried to simplify the process on the web side (still no SIP there) • New transfer request for both blind and attended transfers • Mostly differ on whether we ask to replace a call or not • For blind transfers, no Call-ID is provided • For attended transfers, it is (and transferee is notified) • Janus SIP plugin code tries to handle the differences automatically
  • 28.
    3. Support foractual transfer signalling
  • 29.
    Demoed this inan OpenSIPS workshop
  • 30.
    Alice in acall with Bob...
  • 31.
    Alice decides totransfer the call...
  • 32.
  • 33.
    Bob is notifiedabout the transfer...
  • 34.
    Carol receives thetransfered call
  • 35.
    A quick lookunder the hood (Janus API) Alice Bob Carol Session ID 2882853027068154 5709081044242221 8512736684868091 Main handle 704123326479426 7513373295190055 2834358747375312 Helper handle 7228699494761220 6823091173814012 4398393772247705 Table: Map of sessions and handles in the transfer examples
  • 36.
    A quick lookunder the hood (Janus API) { "janus": "message", "session_id": 2882853027068154, "handle_id": 704123326479426, "transaction": "EocmzzHbvyok", "body": { "request": "transfer", "uri": "sip:carol@192.168.1.110" } }
  • 37.
    A quick lookunder the hood (Janus API) { "janus": "event", "session_id": 2882853027068154, "transaction": "EocmzzHbvyok", "sender": 704123326479426, "opaque_id": "siptest-7LqZsog4X0MG", "plugindata": { "plugin": "janus.plugin.sip", "data": { "sip": "event", "result": { "event": "transferring" }, "call_id": "YOO9sE77w3RqNFTPK18C6sN" } } }
  • 38.
    A quick lookunder the hood (Janus API) { "janus": "event", "session_id": 5709081044242221, "sender": 7513373295190055, "opaque_id": "siptest-XHWhFZWbcjjG", "plugindata": { "plugin": "janus.plugin.sip", "data": { "sip": "event", "result": { "event": "transfer", "refer_id": 991347953, <--- IMPORTANT! "refer_to": "sip:carol@192.168.1.110", "referred_by": "<sip:alice@192.168.1.110:36091>" } } } }
  • 39.
    A quick lookunder the hood (Janus API) { "janus": "message", "session_id": 5709081044242221, "handle_id": 6823091173814012, "transaction": "J1ndk8jz1yuE", "body": { "request": "call", "uri": "sip:carol@192.168.1.110", "refer_id": 991347953 }, "jsep":{ // WebRTC SDP stuff } }
  • 40.
    A quick lookunder the hood (Janus API) { "janus": "event", "session_id": 8512736684868091, "sender": 4398393772247705, "opaque_id": "siptest-748ihwAPS7Lw", "plugindata": { "plugin": "janus.plugin.sip", "data": { "sip": "event", "call_id": "LgvQ1kV2MHluoXlJBe9MQs3", "result": { "event": "incomingcall", "username": "sip:bob@192.168.1.110", "callee": "sip:carol@192.168.1.110", NOTICE ----> "referred_by": "<sip:alice@192.168.1.110:36091>" } } },
  • 41.
    A quick lookunder the hood (Janus API) { "janus": "event", "session_id": 2882853027068154, "transaction": "d7LoWBpinfTP", "sender": 704123326479426, "opaque_id": "siptest-7LqZsog4X0MG", "plugindata": { "plugin": "janus.plugin.sip", "data": { "sip": "event", "result": { "event": "notify", "notify": "refer", "substate": "active", "content-type": "message/sipfrag", "content": "SIP/2.0 180 Ringing" } }
  • 42.
    A quick lookunder the hood (Janus API) { "janus": "event", "session_id": 2882853027068154, "transaction": "d7LoWBpinfTP", "sender": 704123326479426, "opaque_id": "siptest-7LqZsog4X0MG", "plugindata": { "plugin": "janus.plugin.sip", "data": { "sip": "event", "result": { "event": "notify", "notify": "refer", "substate": "active", "content-type": "message/sipfrag", "content": "SIP/2.0 200 OK" } }
  • 43.
    Thanks! Questions? Comments? Getin touch! • https://twitter.com/elminiero • https://twitter.com/meetecho • http://www.meetecho.com