<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Tyler Liu on Medium]]></title>
        <description><![CDATA[Stories by Tyler Liu on Medium]]></description>
        <link>https://medium.com/@tylerlong?source=rss-a5fa945f06b5------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*KvGaaYoVjCbMTq0tyGFVUg.jpeg</url>
            <title>Stories by Tyler Liu on Medium</title>
            <link>https://medium.com/@tylerlong?source=rss-a5fa945f06b5------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 22 Jun 2026 05:27:40 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@tylerlong/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Minimum code to run a WebPhone SDK project]]></title>
            <link>https://medium.com/@tylerlong/minimum-code-to-run-a-webphone-sdk-project-a92603065e40?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/a92603065e40</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 22:28:34 GMT</pubDate>
            <atom:updated>2026-03-17T22:28:34.104Z</atom:updated>
            <content:encoded><![CDATA[<p>Let’s be honest: modern web development can sometimes feel like an endless maze of configuration. Between Webpack, Vite, Parcel, TypeScript, and endless npm install commands, just getting a simple &quot;Hello World&quot; off the ground can take longer than writing the actual code.</p><p>But it doesn’t have to be that way.</p><p>If you want to integrate voice calling into your browser using the RingCentral WebPhone SDK, you don’t need a massive tech stack. You don’t need a build step. You don’t even need a bundler.</p><p>Here is the absolute simplest, minimal code required to get a WebPhone running in a single HTML file using pure Vanilla JavaScript.</p><h4>The Code</h4><p>Save the following code into an index.html file:</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>  &lt;head&gt;&lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;button id=&quot;call-button&quot; type=&quot;button&quot;&gt;Call&lt;/button&gt;<br>    &lt;script src=&quot;https://cdn.jsdelivr.net/npm/ringcentral-web-phone@2.3.3/dist/esm/index.umd.js&quot;&gt;&lt;/script&gt;<br>    &lt;script type=&quot;module&quot;&gt;<br>      const webPhone = new WebPhone({<br>        debug: true,<br>        // sipInfo could be obtained from https://developers.ringcentral.com/api-reference/Device-SIP-Registration/createSIPRegistration<br>        sipInfo,<br>      });<br>      await webPhone.start();<br>      const callButton = document.getElementById(&quot;call-button&quot;);<br>      let activeSession = null;<br>      callButton.addEventListener(&quot;click&quot;, async () =&gt; {<br>        if (activeSession) { // call is ongoing<br>          activeSession.hangup();<br>          return;<br>        }<br>        callButton.textContent = &quot;Hang up&quot;;<br>        activeSession = await webPhone.call(&quot;16506666666&quot;); // the target number to call<br>        activeSession.once(&quot;disposed&quot;, () =&gt; {<br>          activeSession = null;<br>          callButton.textContent = &quot;Call&quot;;<br>        });<br>      });<br>    &lt;/script&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><h4>Breaking It Down</h4><p>That’s it. 30 lines of code. Here is exactly why this is so simple:</p><ul><li><strong>No Bundlers:</strong> We pull the SDK directly from the jsdelivr CDN. No Node modules required.</li><li><strong>Initialization:</strong> We instantiate the WebPhone class and pass it a sipInfo object.</li><li><strong>Action:</strong> We wire up a single HTML button to toggle between making a call (webPhone.call()) and hanging up (activeSession.hangup()). The SDK handles the heavy lifting of WebRTC under the hood.</li></ul><h4>Two Crucial Things to Know</h4><p>Before you test this out, there are two important rules you need to follow for this to work perfectly.</p><p><strong>1. You Must Use a Local Server</strong> You cannot simply double-click the index.html file and open it in your browser via the file:// protocol. While Safari <em>might</em> forgive you, Chrome and most other modern browsers will block microphone access and WebRTC features due to strict security and permission restrictions.</p><ul><li><strong>The Fix:</strong> Host the file using a simple local server. If you have Python installed, run python -m http.server in your folder. If you have Node, run npx http-server. Then navigate to <a href="http://localhost:8000.">http://localhost:8000.</a></li></ul><p><strong>2. The Magic of the </strong><strong>sipInfo Object</strong> You might have noticed that there are no client IDs, client secrets, JWTs, usernames, or passwords in this code. The frontend only cares about one thing: the sipInfo object.</p><ul><li><strong>How to get it:</strong> You will need your API credentials temporarily to generate a sipInfo object via the <a href="https://developers.ringcentral.com/api-reference/Device-SIP-Registration/createSIPRegistration">RingCentral SIP Registration API</a>.</li><li><strong>The best part:</strong> The sipInfo object <strong>does not expire</strong>. Once you generate it (perhaps via an API testing tool like Postman or a quick backend script), you can hardcode it into this vanilla HTML project and never worry about auth tokens or login flows in your UI.</li></ul><h4>Conclusion</h4><p>Integrating a web phone doesn’t require a heavy, complex architecture. By stripping away the build tools and focusing on the core SDK, you can have a functional, browser-based phone ready to make calls in just a few minutes.</p><p>Happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a92603065e40" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Be the First to Say Happy Birthday — Automatically]]></title>
            <link>https://medium.com/ringcentral-developers/be-the-first-to-say-happy-birthday-automatically-19a4aa8a5eb4?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/19a4aa8a5eb4</guid>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[sdk]]></category>
            <category><![CDATA[softphone]]></category>
            <category><![CDATA[birthday]]></category>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Tue, 10 Jun 2025 15:02:19 GMT</pubDate>
            <atom:updated>2025-06-10T15:02:19.564Z</atom:updated>
            <content:encoded><![CDATA[<h3>Be the First to Say Happy Birthday, Automatically</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7kLQV0znbelUAqVzwCgcjQ.png" /></figure><p>Have you ever received a call at exactly 12:00 AM on your birthday? I have, and let me tell you, it’s unforgettable. That moment becomes your very first memory of the day, and it sets the tone. Whether it’s a birthday or New Year’s Eve, being the first to send heartfelt wishes makes a lasting impact.</p><p>Now imagine <em>you</em> being that person for your loved ones, without even staying up late.</p><h4>The Problem: Midnight Wishes Are Easy to Miss</h4><p>We all have good intentions. You want to call your friend or family member at midnight, just like they did for you. But life gets in the way. You fall asleep. You forget. Or you look at the clock and it’s already 12:05, missed your chance to be the first.</p><h4>The Solution: Let Code Do It for You</h4><p>What if a simple program could call your loved one at midnight and deliver your birthday wishes, automatically? With less than 30 lines of code, you can make it happen.</p><p>Here’s how:</p><pre>import Softphone from &quot;ringcentral-softphone&quot;;<br>import waitFor from &quot;wait-for-async&quot;;<br>import fs from &quot;fs&quot;;<br><br>const softphone = new Softphone({<br>  domain: process.env.SIP_INFO_DOMAIN!,<br>  outboundProxy: process.env.SIP_INFO_OUTBOUND_PROXY!,<br>  username: process.env.SIP_INFO_USERNAME!,<br>  password: process.env.SIP_INFO_PASSWORD!,<br>  authorizationId: process.env.SIP_INFO_AUTHORIZATION_ID!,<br>});<br>softphone.enableDebugMode();<br><br>const main = async () =&gt; {<br>  await softphone.register();<br>  const callSession = await softphone.call(process.env.YOUR_FRIENDS_NUMBER!);<br>  callSession.once(&quot;answered&quot;, async () =&gt; {<br>    await waitFor({ interval: 1000 });<br>    const streamer = callSession.streamAudio(<br>      fs.readFileSync(&quot;test.wav&quot;),<br>    );<br>  });<br>};<br>main();</pre><p>This code uses the <a href="https://github.com/ringcentral/ringcentral-softphone-ts">RingCentral Softphone SDK</a>, soon to be known as <strong>Cloud Phone SDK</strong>. It allows you to place automated VoIP calls and stream audio messages, perfect for surprise greetings.</p><h3>What Does the Code Do?</h3><ul><li>Initializes the softphone using your SIP credentials</li><li>Registers with the RingCentral SIP server</li><li>Dials your friend’s number</li><li>Waits for them to answer</li><li>Streams a pre-recorded birthday greeting</li></ul><p>That’s it. No GUI. No distractions. Just pure automation magic.</p><p>Fully runnable code is hosted <a href="https://github.com/tylerlong/happy-birthday-caller/blob/main/src/index.ts">here</a>.</p><h4>Creating the Greeting Audio File</h4><p>You don’t need a fancy studio to make your message. You can generate it from the command line using macOS’s say command:</p><pre>say &quot;Hey Tyler\! Just wanted to give you a quick call to wish you a very happy birthday\! Hope you&#39;re having an amazing day, filled with fun, laughter, and everything you love. You deserve the best - enjoy your day and celebrate big\! Talk to you soon\!&quot; -o test.wav --data-format=LEI16@16000</pre><p>This creates a .wav file in the format required by the SDK. You can record your own voice if you&#39;d rather make it more personal.</p><h4>How to Run the App</h4><p>From your terminal:</p><pre>yarn test</pre><p>Make sure your environment variables (SIP_INFO_*, YOUR_FRIENDS_NUMBER) are properly set.</p><h4>How to Schedule the App to Run on Your Friend’s Birthday</h4><p>To make sure the birthday call goes out at exactly <strong>12:00 AM</strong> on your friend’s special day, use a <strong>cron job</strong> that runs once a year.</p><p>For example, if your friend’s birthday is <strong>July 15</strong>, add this to your crontab:</p><pre>0 0 15 7 * cd /home/tyler/happy-birthday-caller &amp;&amp; /usr/bin/env yarn test &gt;&gt; /home/tyler/happy-birthday-caller/birthday.log 2&gt;&amp;1</pre><ul><li>00 — run at 12:00 AM</li><li>15 — on the 15th day</li><li>7 — of the 7th month (July)</li><li>* — every day of the week (no restriction)</li></ul><p>To edit your crontab:</p><pre>crontab -e</pre><p>Paste the line above, and save.</p><p>That’s it. Your app will run automatically every year on your friend’s birthday, right at midnight, no alarms, no missed moments.</p><h4>Test It First</h4><p>Before relying on it for an actual birthday, <strong>test your setup</strong>. Temporarily change the caller number to your own number and change the cron schedule to run every minute:</p><pre>* * * * * cd /home/tyler/happy-birthday-caller &amp;&amp; /usr/bin/env yarn test &gt;&gt; /home/tyler/happy-birthday-caller/test.log 2&gt;&amp;1</pre><p>Watch the logs and confirm that the call is placed and audio is played as expected.</p><p>Once confirmed, change it back to the real date and your friend’s phone number.</p><h3>Behind the Scenes: Powered by Softphone SDK</h3><p>This birthday greeting example is more than just a fun automation project, it’s a powerful demonstration of what the <a href="https://github.com/ringcentral/ringcentral-softphone-ts">RingCentral Softphone SDK</a> (soon to be rebranded as <strong>Cloud Phone SDK</strong>) can do.</p><p>With just a few lines of JavaScript, you can:</p><ul><li>Programmatically <strong>register a SIP client</strong></li><li><strong>Place outbound VoIP calls</strong></li><li><strong>Detect when the call is answered</strong></li><li><strong>Stream pre-recorded audio</strong> to the callee</li></ul><p>All with full control and event handling, no need to deal with low-level SIP or media stack complexities.</p><p>This birthday call is just one example. The same functionality can power real-world use cases like:</p><ul><li>Automated appointment reminders</li><li>Delivery or pickup voice alerts</li><li>Emergency callouts or school closure notices</li><li>Interactive voice menus (IVRs)</li></ul><p>Whether you’re a hobbyist or building enterprise-grade tools, the Softphone SDK makes it easy to bring voice automation into your apps.</p><h3>Try It for Yourself</h3><p>If this inspired you, imagine what else you can create. With the Softphone SDK, voice is no longer just for phones, it’s programmable, flexible, and ready for your ideas.</p><blockquote><em>Voice is powerful. With the right SDK, it’s programmable too.</em></blockquote><p><a href="https://github.com/ringcentral/ringcentral-softphone-ts">RingCentral Softphone SDK</a> (soon to be rebranded as <strong>Cloud Phone SDK</strong>).</p><p>Start building today. 🎉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=19a4aa8a5eb4" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ringcentral-developers/be-the-first-to-say-happy-birthday-automatically-19a4aa8a5eb4">Be the First to Say Happy Birthday — Automatically</a> was originally published in <a href="https://medium.com/ringcentral-developers">RingCentral Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Empower Your AI with Real-Time Phone Call Intelligence: RingCentral Softphone SDK]]></title>
            <link>https://medium.com/ringcentral-developers/empower-your-ai-with-real-time-phone-call-intelligence-ringcentral-softphone-sdk-5356e38fb3d9?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/5356e38fb3d9</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[sdk]]></category>
            <category><![CDATA[ringcentral-developer]]></category>
            <category><![CDATA[guide]]></category>
            <category><![CDATA[softphone]]></category>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Tue, 03 Jun 2025 15:24:58 GMT</pubDate>
            <atom:updated>2025-06-05T17:10:53.118Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u_TLI9QRdRvfGXhJxHcx7A.png" /></figure><p>For AI startups building the future of voice-driven solutions, accessing real-time phone audio is mission-critical. <strong>RingCentral’s Softphone SDK</strong> cuts through the complexity, giving developers instant access to call audio streams, so you can focus on what matters: <strong>building groundbreaking AI features</strong>.</p><h4>Why AI Startups Choose RingCentral</h4><p>1️⃣ <strong>Instant Access to Call Audio Streams</strong></p><ul><li>Capture inbound/outbound call audio in real time for transcription, emotion detection, live translation, or AI agent training.</li><li><strong>Plug-and-play TypeScript SDK</strong>: Skip SIP protocol headaches. Focus on your AI models, not telephony infrastructure.</li><li><strong>Multi-Codec Support</strong> (OPUS, PCMU): Process high-fidelity audio optimized for AI/ML pipelines.</li></ul><pre>// Stream call audio directly to your AI model  <br>callSession.on(&quot;rtpPacket&quot;, (rtpPacket) =&gt; {  <br>  if (rtpPacket.header.payloadType === softphone.codec.id) {  <br>    yourAIPipeline.analyze(rtpPacket.payload); // 🚀 Unlock real-time insights  <br>  }  <br>}); </pre><p>2️⃣ <strong>Build Fast, Scale Faster</strong></p><ul><li><strong>Zero per-minute fees</strong>: Predictable pricing for startups scaling AI call solutions.</li><li><strong>Programmatic SIP setup</strong>: Automate credentials via <a href="https://developers.ringcentral.com/api-reference/Devices/readDeviceSipInfo">REST API</a> — no manual config.</li><li><strong>Active call management</strong>: Run concurrent instances to handle high-volume AI workflows.</li></ul><p>3️⃣ <strong>AI-Ready Features</strong></p><ul><li><strong>Inbound/outbound call control</strong>: Create AI agents, call coaches, or automated QA tools.</li><li><strong>DTMF support</strong>: Integrate voice + touch-tone interactions (e.g., IVR + AI assistants).</li><li><strong>Call transfer/hangup</strong>: Dynamically route calls based on AI decisions.</li></ul><h4>Get Started in 5 Minutes</h4><pre>yarn install ringcentral-softphone  # Install</pre><p><strong>👉 Explore Demos:</strong> <a href="https://github.com/ringcentral/ringcentral-softphone-ts/tree/main/demos">GitHub Examples</a></p><h4>Why Reinvent the Wheel?</h4><p>While others force you to wrestle with telephony protocols, <strong>RingCentral gives you:</strong></p><p>✅ <strong>TypeScript-first SDK</strong>: Modern, type-safe, and battle-tested.<br>✅ <strong>Enterprise-grade reliability</strong>: Built by a leader in cloud communications.<br>✅ <strong>End-to-end audio pipelines</strong>: From codec decoding to real-time streaming — it’s all handled.</p><p><strong>Your AI, Their Voices — No Limits.</strong><br><a href="https://ringcentral.com/softphone-sdk">Start Building Today</a></p><p><em>AI startups: Turn every phone call into a data goldmine. With RingCentral, the future of voice intelligence is just a line of code away.</em> 🧠🔊</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5356e38fb3d9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ringcentral-developers/empower-your-ai-with-real-time-phone-call-intelligence-ringcentral-softphone-sdk-5356e38fb3d9">Empower Your AI with Real-Time Phone Call Intelligence: RingCentral Softphone SDK</a> was originally published in <a href="https://medium.com/ringcentral-developers">RingCentral Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RingCentral Web Phone SDK 2.0]]></title>
            <link>https://medium.com/@tylerlong/ringcentral-web-phone-sdk-2-0-8e55a4f6e2b2?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/8e55a4f6e2b2</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Fri, 25 Oct 2024 21:29:53 GMT</pubDate>
            <atom:updated>2024-10-30T17:41:15.933Z</atom:updated>
            <content:encoded><![CDATA[<p>It is a complete rewrite of the SDK compared to 1.x versions.</p><h4><strong>Why rewrite?</strong></h4><p>The rewriting is to get rid of SIP.js.</p><p><strong>SIP.js is no longer actively maintained</strong></p><p>The last release of SIP.js was in October 2022, and it hasn’t been updated since. Depending on an unmaintained library poses risks, including potential incompatibility with future browser updates and WebRTC changes. By moving away from SIP.js, we ensure that our SDK remains compatible with evolving web standards.</p><p><strong>SIP.js lacks support for essential RingCentral features</strong></p><p>SIP.js was not built with RingCentral’s specific requirements in mind. To support critical functionalities like confirming receipt, sending calls to voicemail, declining, forwarding, replying, call recording (start/stop), call flipping, and parking, we had to patch SIP.js heavily. Managing these patches was inefficient, and developing our own signaling library is a more sustainable approach.</p><p><strong>SIP signaling is simple enough to implement in-house</strong></p><p>SIP signaling itself is a relatively straightforward protocol. By implementing the SIP signaling in-house, we can avoid the overhead and complexity introduced by SIP.js while gaining full control over the signaling flow.</p><p><strong>Decoupling SIP signaling from WebRTC</strong></p><p>SIP.js tightly couples SIP signaling with WebRTC. By decoupling these two components, we allow you to run a web phone with a real/dummy SIP client, which is essential for scenarios where you need to run multiple web phones in multiple tabs.</p><h4><strong>Breaking changes</strong></h4><p><strong>API changes</strong></p><p>2.x version is a complete rewrite of the RingCentral Web Phone SDK. The API is completely different from 1.x version.</p><p><strong>ringing audio</strong></p><p>This SDK doesn’t play ringing audio when there is incoming call or outgoing call. It’s up to the developer/app to play the audio. It’s a by design change. We made this change because we want to give the developer/app more flexibility. And playing ringing audio is not a core feature of the SDK. It’s more about how the app interacts with end users.</p><p><strong>call forward</strong></p><p>SDK 1.x treats forwarding as answering the call and then transfer the call. SDK 2.x treats forwarding as sending a SIP message to the SIP server to forward the call. I would like to say this is more like a bug fix than a behavior change.</p><p><strong>&lt;audio /&gt;</strong></p><p>SDK 1.x requires you to provide &lt;audio /&gt; elements to play remote audio. SDK 2.x will create &lt;audio /&gt; elements on demand. You don’t need to provide &lt;audio /&gt; elements.</p><h4>Summary</h4><p>In this article, I included information about why we released a brand new 2.0 version and I also listed all the breaking changes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8e55a4f6e2b2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to disable auto token refreshment for RingCentral JavaScript SDK]]></title>
            <link>https://medium.com/ringcentral-developers/how-to-disable-auto-token-refreshment-for-ringcentral-javascript-sdk-461d7982ed35?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/461d7982ed35</guid>
            <category><![CDATA[sdk]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[ringcentral]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Mon, 22 Apr 2024 22:52:40 GMT</pubDate>
            <atom:updated>2025-03-04T19:02:28.164Z</atom:updated>
            <content:encoded><![CDATA[<h4>Give you all the control over token management</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F4lwUn19xq3vwWj3-qL_ng.jpeg" /></figure><p>RingCentral JavaScript SDK is available here: <a href="https://www.npmjs.com/package/@ringcentral/sdk">https://www.npmjs.com/package/@ringcentral/sdk</a></p><p>It’s source code is here: <a href="https://github.com/ringcentral/ringcentral-js/tree/master/sdk">https://github.com/ringcentral/ringcentral-js/tree/master/sdk</a></p><h4>The SDK</h4><p>A sample application is like this:</p><pre>import { SDK } from &#39;@ringcentral/sdk&#39;;<br><br>const sdk = new SDK({<br>  server: process.env.RINGCENTRAL_SERVER_URL,<br>  clientId: process.env.RINGCENTRAL_CLIENT_ID,<br>  clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,<br>});<br><br>// we print all the HTTP requests<br>const client = sdk.client();<br>client.on(client.events.requestSuccess, (apiResponse) =&gt; {<br>  console.log(&#39;We made an API call to&#39;, apiResponse.url);<br>});<br><br>const platform = sdk.platform();<br><br>const main = async () =&gt; {<br>  await platform.login({<br>    jwt: process.env.RINGCENTRAL_JWT_TOKEN,<br>  });<br>  await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);<br>};<br>main();</pre><p>Sample output:</p><pre>We made an API call to https://platform.ringcentral.com/restapi/oauth/token<br>We made an API call to https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~</pre><p>The first line of output is generated by this API call:</p><pre>await platform.login({<br>  jwt: process.env.RINGCENTRAL_JWT_TOKEN,<br>});</pre><p>The second line of output is generated by this API call:</p><pre>await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);</pre><h4>The interesting fact</h4><p>RingCentral access token expires in 1 hour. What will happen if we invoke an API after 2 hours? You may expect an exception. Because you cannot invoke RingCentral API with an expired access token. Will there be an exception?</p><p>Let’s try:</p><pre>import { SDK } from &#39;@ringcentral/sdk&#39;;<br>import waitFor from &#39;wait-for-async&#39;;<br><br>...<br>await pltaform.login({<br>  jwt: process.env.RINGCENTRAL_JWT_TOKEN<br>});<br>await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);<br>await waitFor({ interval: 7200000 }); // 2 hours<br>await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);</pre><p>The output is:</p><pre>We made an API call to https://platform.ringcentral.com/restapi/oauth/token<br>We made an API call to https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~<br>We made an API call to https://platform.ringcentral.com/restapi/oauth/token<br>We made an API call to https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~</pre><p>Surprisingly, there is no exception! The first two lines of output are same as before. There are two more lines of output. The last line of output is for our API call, there is no exception. What is the 3rd line? It says so:</p><pre>We made an API call to https://platform.ringcentral.com/restapi/oauth/token</pre><p>It turns out that the SDK automatically refreshed the access token for us! It’s magic! Let’s dive into the source code of the SDK and see how it work:</p><p>When we invoke the API, we use the platform.get(...) method: <a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L787-L789">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L787-L789</a></p><p>which invokes the platform.send(...) method: <a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L761">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L761</a></p><p>which invokes the platform.sendRequest(…) method: <a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L716">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L716</a></p><p>which invokes the platform.inflateRequest(…) method: <a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L698">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L698</a></p><p>which invokes the platform.ensureLoggedIn(…) method: <a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L807">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L807</a></p><p>which invokes the platform.refresh(…) method:<a href="https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L810">https://github.com/ringcentral/ringcentral-js/blob/77484be7fc946e72f1b119fd31e8a49d72e31497/sdk/src/platform/Platform.ts#L810</a></p><p>That’s such a long method call chain! Let me summarize what it does: when you invoke an API, the SDK will check your access token expire time, if it expires, the SDK will automatically refresh the token for you.</p><p>This interesting fact is both a blessing and a curse. For people scratching quick and dirty simple applications, it saves their headache of refreshing the token themselves. But for complicated enterprise applications, this token auto refreshing feature could be really annoying.</p><h4>The issue/problem</h4><p>More often than not, developers want to manage token lifecycle themselves, especially for complicated enterprise applications. For example, some developer may store the token in a central database, and share the token among multiple applications (or multiple instances of the same application code base).</p><p>If multiple applications sharing the same token are all use the JavaScript SDK, by default, the SDK will automatically refresh the token, which will cause several issues:</p><ul><li>token synchronization: one application refreshes the token will cause all other applications to have a revoked token. Thus, it is necessary to sync the token to all other applications.</li><li>racing condition: if two/multiple applications try to refresh the token at the same time, RingCentral server will make sure that only one of them will succeed. You may encounter token refresh failure due to this reason.</li><li>hard to take full control of the token lifecycle: let’s say you write a dedicated application to maintain the token’s lifecycle. This specially coded app will make sure that tokens are properly refreshed and maintained. If all the token consuming applications also refresh the token, it will again bring up the token synchronize and racing condition problem.</li></ul><p>Can we just get rid of this problem?</p><h4>The solution</h4><p>First of all, I would like to emphasize that this solution is not necessary for simple applications. Let’s say you have an simple desktop application that you start once a day to send a fax. If it works, you don’t need to change it. You don’t need to care about how the token is refreshed.</p><p>If your application is distributed, tokens are shared among different servers/clients, and you want to centrally manage the token’s lifecycle. In the meantime, you don’t want anything magical happen to your token. You want full control over token’s lifecycle. If it sounds like your case, then the solution is for you.</p><p>The solution is very simple:</p><pre>platform.ensureLoggedIn = async () =&gt; null;</pre><p>Just rewrite the platform.ensureLoggedIn method and make sure it does nothing. This is because, we want to manage the token ourselves, we don’t need the ensureLoggedIn feature provided by the SDK. Please note that, if you are using the platform.ensureLoggedIn method directly in your code base, you need to change it to some other code. Since this method has been overridden to do nothing.</p><p>Then how do we refresh the token? easy:</p><pre>await platform.refresh();</pre><p>Or you may setup a timer to do it every 30 minutes:</p><pre>setInterval(async () =&gt; {<br>  await platform.refresh();<br>}, 1800000);</pre><p>Or you may create a dedicated application to refresh the token and let the app trigger by cron jobs.</p><p>You have the full flexibility to decide when and how you refresh your token.</p><p>Please note that, if multiple apps are using the token, you still need to sync the new token to all apps after token refreshment. This is inevitable. But at least, you token is bing refreshed and managed in a central place. There is no more magic happening to your tokens.</p><h4>Source code</h4><p>The source code for this article is available here: <a href="https://github.com/tylerlong/rc-js-sdk-no-auto-refresh-token-demo">https://github.com/tylerlong/rc-js-sdk-no-auto-refresh-token-demo</a></p><h4>Update</h4><p>Nowadays I would recommend you to use <a href="https://github.com/ringcentral/ringcentral-extensible">https://github.com/ringcentral/ringcentral-extensible</a> instead. This SDK is also officially supported. It doesn’t touch your token by default. And it has way better TypeScript support.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=461d7982ed35" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ringcentral-developers/how-to-disable-auto-token-refreshment-for-ringcentral-javascript-sdk-461d7982ed35">How to disable auto token refreshment for RingCentral JavaScript SDK</a> was originally published in <a href="https://medium.com/ringcentral-developers">RingCentral Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code a RingCentral WebSocket SDK from scratch]]></title>
            <link>https://medium.com/@tylerlong/code-a-ringcentral-websocket-sdk-from-scratch-bab68af5258?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/bab68af5258</guid>
            <category><![CDATA[ringcentral]]></category>
            <category><![CDATA[websocket]]></category>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Tue, 18 Apr 2023 20:59:01 GMT</pubDate>
            <atom:updated>2023-04-19T21:42:39.227Z</atom:updated>
            <content:encoded><![CDATA[<h4>Technical details about RingCentral WebSocket API</h4><h4>What is RingCentral Subscription API?</h4><p>A subscription refers to how events are delivered to an application. Events are synonymous with notifications, and refer to messages that are sent after they are triggered by an action, or state-change on the platform.</p><p>For example, you may subscribe to the following events:</p><ul><li>SMS received</li><li>Presence/availability changed</li><li>Call started/ended</li></ul><h4>What is RingCentral WebSocket API?</h4><p>It is mainly a new way to create subscription and receive events. Another popular way is to use WebHook(for more information about RingCentral WebHook, please read <a href="https://medium.com/ringcentral-developers/common-questions-on-webhooks-8dd3550f4525">this article</a>). WebHook requires you to have a public URL that RingCentral server can access. But if your app doesn’t have a public URL (most non-web applications don’t have an URL at all), you cannot use WebHook. In such case, you may use WebSocket to create subscription and receive events.</p><p>Please note that, RingCentral WebSocket API can do more than just subscriptions. For example, you may send “RESTful” API calls via WebSocket protocol. But in this article, we will <strong>focus on subscriptions</strong>.</p><h4>Official RingCentral WebSocket SDKs</h4><p>We have provided the following official WebSocket SDKs:</p><ul><li>TypeScript/JavaScript: <a href="https://github.com/ringcentral/ringcentral-extensible/tree/master/packages/extensions/ws">https://github.com/ringcentral/ringcentral-extensible/tree/master/packages/extensions/ws</a></li><li>C#/.Net: <a href="https://github.com/ringcentral/RingCentral.Net/tree/master/RingCentral.Net.WebSocket">https://github.com/ringcentral/RingCentral.Net/tree/master/RingCentral.Net.WebSocket</a></li><li>Java: <a href="https://github.com/ringcentral/ringcentral-websocket-java">https://github.com/ringcentral/ringcentral-websocket-java</a></li></ul><p>At the moment, we do not provide official SDKs for other programming languages. We may provide more SDKs in the future, but we are unlikely able to provide SDKs for every programming language existed (there are thousands of them). That’s why I am drafting this article. I will cover the details for how you can code your own RingCentral WebSocket SDK.</p><h4>Prerequisites</h4><p>We assume that you are already familiar with basic RingCentral API programming.</p><ul><li>You should have a RingCentral developer account.</li><li>You should have create a RingCentral App.</li><li>Your RingCentral App must have permission “WebSocket Subscriptions”. At the moment the permission is internal. You may create a ticket to add it: <a href="https://developers.ringcentral.com/support/create-case">https://developers.ringcentral.com/support/create-case</a></li><li>You should know where to find the credentials (clientId/clientSecret/JWT token…etc) for testing purpose.</li><li>You should know how to get access token and make API calls.</li></ul><p>If you are not familiar with the things I mentioned above, please check the latest articles from my colleague Desika Srinivasan and A Lohith:</p><ul><li><a href="https://medium.com/@vinaysd17/how-to-build-a-communication-app-with-ringcentral-api-rest-fc23e130949d">Part 1: How to build a communication app with RingCentral API [REST]</a></li><li><a href="https://medium.com/@vinaysd17/part-2-how-to-build-a-communication-app-with-ringcentral-api-rest-8b9f1d579124">Part 2: How to build a communication app with RingCentral API [REST]</a></li><li><a href="https://medium.com/@vinaysd17/part-3-how-to-build-a-communication-app-with-ringcentral-api-rest-8c57229dad14">Part 3: How to build a communication app with RingCentral API [REST]</a></li></ul><p>Please <strong>note that</strong>, the information shared in the 3 articles are very detailed-oriented and low-level. You probably want to use one of our REST SDKs to simplify the coding part. For example, to make API call using our Java SDK, refer to this example: <a href="https://github.com/ringcentral/ringcentral-java/blob/master/src/test/java/com/ringcentral/LowLevelAPITest.java">https://github.com/ringcentral/ringcentral-java/blob/master/src/test/java/com/ringcentral/LowLevelAPITest.java</a></p><p>You are supposed to know at least one programming language. We will use Java as example, but the knowledge shared here should apply to other languages as well.</p><h4>Connect to WebSocket Server</h4><p>Make A REST API call to /restapi/oauth/wstoken</p><pre>restClient.post(&quot;/restapi/oauth/wstoken&quot;)</pre><p>The API response is a JSON string with the following data fields:</p><pre>class WsToken {<br>    public String uri;<br>    public String ws_access_token;<br>    public int expires_in;<br>}</pre><p>A sample response looks like this:</p><pre>{<br>  &quot;uri&quot; : &quot;wss://ws-api.devtest.ringcentral.com/ws&quot;,<br>  &quot;ws_access_token&quot; : &quot;xxxxx&quot;,<br>  &quot;expires_in&quot; : 60<br>}</pre><p>With the information above, we can connect to the WebSocket server using our favorite WebSocket client library:</p><pre>String wsUri = wsToken.uri + &quot;?access_token=&quot; + wsToken.ws_access_token;<br>MyWebSocketClient myClient = new MyWebSocketClient(URI.create(wsUri));<br>myClient.connect();</pre><h4>Post messages to WebSocket server</h4><p>In this section, we are going to create a subscription by posting messages to WebSocket server. You may post message to WebSocket server to invoke lots of different API endpoints. But here we take subscription creation for example, because in the next section we are going to demo how to receive notifications.</p><p>A message that you send to the WebSocket server contains two parts: the header and the body. But wait, does a WebSocket message support a header at all? Nope, it doesn’t. So by design, the WebSocket server requires us to send it an array contains both the header and the the body, like this: [header, body].</p><p>The header data structure is like this:</p><pre>class RequestHeaders {<br>    public String type;<br>    public String messageId;<br>    public String method;<br>    public String path;<br>}</pre><p>We can build a header object like this:</p><pre>RequestHeaders requestHeaders = new RequestHeaders();<br>requestHeaders.method = &quot;POST&quot;;<br>requestHeaders.path = &quot;/restapi/v1.0/subscription&quot;;<br>requestHeaders.type = &quot;ClientRequest&quot;;<br>requestHeaders.messageId = UUID.randomUUID().toString();</pre><p>How about the body? It turns out that the body is the same as you make a REST API call. We can look it up in the API reference page. For HTTP POST /restapi/v1.0/subscription, check this page: <a href="https://developers.ringcentral.com/api-reference/Subscriptions/createSubscription">https://developers.ringcentral.com/api-reference/Subscriptions/createSubscription</a> We can build a body object like this:</p><pre>SubscriptionRequestBody requestBody = new SubscriptionRequestBody();<br>requestBody.deliveryMode = new SubscriptionRequestBodyDeliveryMode();<br>requestBody.deliveryMode.transportType = &quot;WebSocket&quot;;<br>requestBody.eventFilters = new String[]{&quot;/restapi/v1.0/account/~/extension/~/message-store&quot;};</pre><p>Let’s put the header and body into an array and convert the array into a JSON string:</p><pre>Object[] array = new Object[2];<br>array[0] = requestHeaders;<br>array[1] = requestBody;<br>String messageString = Utils.gson.toJson(array);</pre><p>We may send the messageString to the WebSocket server:</p><pre>myClient.send(messageString);</pre><p>If everything goes well, the server will send you a reply message. The reply message is also an array containing two parts: the header and the body. Here a I provide a sample response for your reference:</p><pre>[<br>  {<br>    &quot;type&quot;: &quot;ClientRequest&quot;,<br>    &quot;messageId&quot;: &quot;9fb4f43b-5b9d-4eb3-a8ac-9df81b5de783&quot;,<br>    &quot;status&quot;: 200,<br>    &quot;headers&quot;: {<br>      &quot;Server&quot;: &quot;nginx&quot;,<br>      &quot;Date&quot;: &quot;Tue, 18 Apr 2023 20:25:16 GMT&quot;,<br>      &quot;Content-Type&quot;: &quot;application/json&quot;,<br>      &quot;WSG-SubscriptionId&quot;: &quot;2db5fcc4-c515-423e-8c2f-fa9b78dd9a55&quot;,<br>      &quot;WSG-SubscriptionTTL&quot;: &quot;86399&quot;,<br>      &quot;X-Rate-Limit-Group&quot;: &quot;medium&quot;,<br>      &quot;X-Rate-Limit-Limit&quot;: &quot;999&quot;,<br>      &quot;X-Rate-Limit-Remaining&quot;: &quot;998&quot;,<br>      &quot;X-Rate-Limit-Window&quot;: &quot;60&quot;,<br>      &quot;RoutingKey&quot;: &quot;SJC12P01&quot;,<br>      &quot;RCRequestId&quot;: &quot;2119cb78-de27-11ed-8151-005056a61ee6-18&quot;<br>    }<br>  },<br>  {<br>    &quot;uri&quot;: &quot;/restapi/v1.0/subscription/2db5fcc4-c515-423e-8c2f-fa9b78dd9a55&quot;,<br>    &quot;id&quot;: &quot;2db5fcc4-c515-423e-8c2f-fa9b78dd9a55&quot;,<br>    &quot;creationTime&quot;: &quot;2023-04-18T20:25:16.139Z&quot;,<br>    &quot;status&quot;: &quot;Active&quot;,<br>    &quot;eventFilters&quot;: [<br>      &quot;/restapi/v1.0/account/233676004/extension/233676004/message-store&quot;<br>    ],<br>    &quot;expirationTime&quot;: &quot;2023-04-19T20:25:16.139Z&quot;,<br>    &quot;expiresIn&quot;: 86399,<br>    &quot;deliveryMode&quot;: {<br>      &quot;transportType&quot;: &quot;WebSocket&quot;,<br>      &quot;encryption&quot;: false<br>    }<br>  }<br>]</pre><p>There is a text field “expiresIn”: 86399 , but don’t worry about its expiration. Because by design the WebSocket server will refresh it automatically. You don’t need to refresh it yourself.</p><h4>Receive notifications</h4><p>This part is relatively simple. With the subscription created, what we need to do is to listen to messages from server side and pick the ones that we are interested in. A typical notification message looks like this:</p><pre>[<br>  {<br>    &quot;type&quot;: &quot;ServerNotification&quot;,<br>    &quot;messageId&quot;: &quot;2661273919712564939&quot;,<br>    &quot;status&quot;: 200,<br>    &quot;headers&quot;: {<br>      &quot;RCRequestId&quot;: &quot;265348d0-de27-11ed-944a-005056a62c0c&quot;<br>    }<br>  },<br>  {<br>    &quot;uuid&quot;: &quot;2661273919712564939&quot;,<br>    &quot;event&quot;: &quot;/restapi/v1.0/account/233676004/extension/233676004/message-store&quot;,<br>    &quot;timestamp&quot;: &quot;2023-04-18T20:25:24.611Z&quot;,<br>    &quot;subscriptionId&quot;: &quot;2db5fcc4-c515-423e-8c2f-fa9b78dd9a55&quot;,<br>    &quot;ownerId&quot;: &quot;233676004&quot;,<br>    &quot;body&quot;: {<br>      &quot;accountId&quot;: 233676004,<br>      &quot;extensionId&quot;: 233676004,<br>      &quot;lastUpdated&quot;: &quot;2023-04-18T20:25:15.775Z&quot;,<br>      &quot;changes&quot;: [<br>        {<br>          &quot;type&quot;: &quot;Pager&quot;,<br>          &quot;newCount&quot;: 2,<br>          &quot;updatedCount&quot;: 0,<br>          &quot;newMessageIds&quot;: [<br>            12756735005,<br>            12756737005<br>          ]<br>        }<br>      ]<br>    }<br>  }<br>]</pre><p>Again this message is an array containing two parts: the header and the body. And the header has “type”: “ServerNotification” . The body is identical to a WebHook notification’s body. You may check the specification of all the notification types here: <a href="https://developers.ringcentral.com/api-reference/Account-Presence-Event">https://developers.ringcentral.com/api-reference/Account-Presence-Event</a>.</p><h4>Known limitations</h4><p>It is known that each WebSocket connection can only have one active subscription. If you try to create more, previous ones will be overwritten. If you need to create multiple subscriptions, you need to create multiple WebSocket connections.</p><h4>Sample code for your reference</h4><p>Please refer to the implementation of the official <a href="https://github.com/ringcentral/ringcentral-websocket-java">RingCentral WebSocket Java SDK</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bab68af5258" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Create WebSocket subscriptions using RingCentral JavaScript SDKs]]></title>
            <link>https://medium.com/@tylerlong/create-websocket-subscriptions-using-ringcentral-javascript-sdks-1204ce5843b8?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/1204ce5843b8</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Wed, 18 Jan 2023 22:14:39 GMT</pubDate>
            <atom:updated>2025-05-15T18:31:08.889Z</atom:updated>
            <content:encoded><![CDATA[<h4>A quick start guide for RingCentral WebSocket Subscriptions</h4><h4>What are subscriptions for?</h4><p>First things first, why do you need to create subscriptions? If your application needs to get notified when something happens (like new sms received, new incoming call…etc), you need to create subscriptions to those events. Creating a subscription is like telling RingCentral server that your application is interested in some events. Then your application will get notified when such such events occur.</p><h4><strong>Different ways to create subscriptions</strong></h4><p>At the moment, there are 2 official ways to create subscriptions: WebHook and WebSocket. (There was a legacy way to use PubNub to create subscriptions that we do not recommend any more).</p><p>In this article, we will focus on creating subscriptions using WebSocket. If you are interesting in WebHooks, please read <a href="https://medium.com/ringcentral-developers/common-questions-on-webhooks-8dd3550f4525">this article</a> instead.</p><h4>Prerequisites</h4><p>Your RingCentral application needs to have the “WebSocket Subscriptions” permission.</p><p>If for some reason you cannot enable this permission for your application, please <a href="https://developers.ringcentral.com/support/create-case"><strong>create a support case</strong></a> here.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/834/1*9J491NcvpTF8Wb7Pw0V7cA.png" /></figure><h4>Recommended way</h4><p>You are recommended to use <a href="https://www.npmjs.com/package/@rc-ex/core">@rc-ex/core</a> and <a href="https://www.npmjs.com/package/@rc-ex/ws">@rc-ex/ws</a>. They have excellent TypeScript support and they are actively maintained.</p><pre>import RingCentral from &#39;@rc-ex/core&#39;;<br>import WebSocketExtension from &#39;@rc-ex/ws&#39;;<br><br>const rc = new RingCentral({<br>  server: process.env.RINGCENTRAL_SERVER_URL,<br>  clientId: process.env.RINGCENTRAL_CLIENT_ID,<br>  clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,<br>});<br>const wsExtension = new WebSocketExtension();<br><br>const main = async () =&gt; {<br>  await rc.authorize({<br>    jwt: process.env.RINGCENTRAL_JWT_TOKEN!,<br>  });<br>  // install the WebSocket extension<br>  await rc.installExtension(wsExtension);<br><br>  // subscribe<br>  await wsExtension.subscribe(<br>    [&#39;/restapi/v1.0/account/~/extension/~/message-store&#39;],<br>    evt =&gt; {<br>      console.log(JSON.stringify(evt, null, 2));<br>    }<br>  );<br><br>  // trigger an event, optional<br>  const ext = await rc.restapi().account().extension().get();<br>  await rc<br>    .restapi()<br>    .account()<br>    .extension()<br>    .companyPager()<br>    .post({<br>      from: {extensionId: ext.id?.toString()},<br>      to: [{extensionId: ext.id?.toString()}],<br>      text: &#39;Hello world!&#39;,<br>    });<br>};<br><br>main();</pre><p>In the sample code above, we are using the <a href="https://github.com/ringcentral/ringcentral-extensible">RingCentral Extensible SDK</a>. RingCentral Extensible is a SDK with a tiny core (@rc-ex/core) and lots of extensions. It is an endeavor to get rid of bloated SDK. You install extensions on demand. In order to support WebSocket, we also installed the @rc-ex/ws extension.</p><p>The code is pretty straightforward and self-explanatory, just remember to read the inline comments in the code snippet. There is a section to trigger an event, which is optional, you probably don’t need it in your own code.</p><h4>For <a href="https://www.npmjs.com/package/@ringcentral/sdk">@ringcentral/sdk</a> users</h4><p>If you are existing user of the <a href="https://github.com/ringcentral/ringcentral-js">official JavaScript SDK</a> <a href="https://www.npmjs.com/package/@ringcentral/sdk">@ringcentral/sdk</a> and you’d like to stick to it (instead of switching to the <a href="https://github.com/ringcentral/ringcentral-extensible">RingCentral Extensible SDK</a>), there is a solution for you:</p><pre>import {SDK} from &#39;@ringcentral/sdk&#39;;<br>import RingCentral from &#39;@rc-ex/core&#39;;<br>import WebSocketExtension from &#39;@rc-ex/ws&#39;;<br>import RcSdkExtension from &#39;@rc-ex/rcsdk&#39;;<br><br>const sdk = new SDK({<br>  server: process.env.RINGCENTRAL_SERVER_URL,<br>  clientId: process.env.RINGCENTRAL_CLIENT_ID,<br>  clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,<br>});<br>const platform = sdk.platform();<br>const rc = new RingCentral();<br>const rcsdkExtension = new RcSdkExtension({rcSdk: sdk});<br>const wsExtension = new WebSocketExtension();<br><br>const main = async () =&gt; {<br>  await platform.login({<br>    username: process.env.RINGCENTRAL_USERNAME,<br>    extension: process.env.RINGCENTRAL_EXTENSION,<br>    password: process.env.RINGCENTRAL_PASSWORD,<br>  });<br><br>  // install the extensions<br>  await rc.installExtension(rcsdkExtension);<br>  await rc.installExtension(wsExtension);<br><br>  // subscribe<br>  await wsExtension.subscribe(<br>    [&#39;/restapi/v1.0/account/~/extension/~/message-store&#39;],<br>    evt =&gt; {<br>      console.log(JSON.stringify(evt, null, 2));<br>    }<br>  );<br><br>  // trigger an event, optional<br>  const r = await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);<br>  const ext = await r.json();<br>  platform.post(&#39;/restapi/v1.0/account/~/extension/~/company-pager&#39;, {<br>    from: {extensionId: ext.id},<br>    to: [{extensionId: ext.id}],<br>    text: &#39;Hello world!&#39;,<br>  });<br>};<br><br>main();</pre><p>The code is very similar to the code we provided to new developers above. Please read the code explanation of previous section if you haven’t done so.</p><p>The different part is we are using @rc-ex/rcsdk to make <a href="https://www.npmjs.com/package/@ringcentral/sdk">@ringcentral/sdk</a> an extension of the <a href="https://github.com/ringcentral/ringcentral-extensible">RingCentral Extensible SDK</a>. You may continue to use <a href="https://www.npmjs.com/package/@ringcentral/sdk">@ringcentral/sdk</a> in your projects. And the WebSocket Subscriptions is supported by @rc-ex/ws. <a href="https://www.npmjs.com/package/@ringcentral/sdk">@ringcentral/sdk</a> and @rc-ex/ws could work toghether without issue.</p><h4>For existing developers who don’t want to change their code</h4><p>Let’s say you are already using both @ringcentral/sdk and @ringcentral/subscriptions and you really do not want to change your code. We have you covered!</p><p>@ringcentral/subscriptions is powered by WebSocket instead of PubNub since 5.0.0. And you just upgrade @ringcentral/subscriptions to latest version and no more coding change is required.</p><p>Please note that, @ringcentral/subscriptions is just a proxy library to @rc-ex/ws. Real work is done by @rc-ex/ws. You are recommended to use @rc-ex/ws instead.</p><p>Since you do not need to change your code, you probably do not need any sample code snippets. But here I provide one anyway:</p><pre>import {SDK} from &#39;@ringcentral/sdk&#39;;<br>import {Subscriptions} from &#39;@ringcentral/subscriptions&#39;;<br><br>// init<br>const sdk = new SDK({<br>  server: process.env.RINGCENTRAL_SERVER_URL,<br>  clientId: process.env.RINGCENTRAL_CLIENT_ID,<br>  clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,<br>});<br>const platform = sdk.platform();<br>const subscriptions = new Subscriptions({<br>  sdk: sdk,<br>});<br><br>const main = async () =&gt; {<br>  // login<br>  await platform.login({<br>    username: process.env.RINGCENTRAL_USERNAME,<br>    extension: process.env.RINGCENTRAL_EXTENSION,<br>    password: process.env.RINGCENTRAL_PASSWORD,<br>  });<br><br>  // subscribe<br>  const subscription = subscriptions.createSubscription();<br>  subscription.on(subscription.events.notification, evt =&gt; {<br>    console.log(JSON.stringify(evt, null, 2));<br>  });<br>  await subscription<br>    .setEventFilters([&#39;/restapi/v1.0/account/~/extension/~/message-store&#39;])<br>    .register();<br><br>  // trigger an event, optional<br>  const r = await platform.get(&#39;/restapi/v1.0/account/~/extension/~&#39;);<br>  const ext = await r.json();<br>  platform.post(&#39;/restapi/v1.0/account/~/extension/~/company-pager&#39;, {<br>    from: {extensionId: ext.id},<br>    to: [{extensionId: ext.id}],<br>    text: &#39;Hello world!&#39;,<br>  });<br>};<br><br>main();</pre><h4>Sample output</h4><p>We have provided 3 different solutions for different developers and use cases. No matter which solution you choose, the final result should be similar. If everything goes well, a notification message will be printed to console. Below is just a sample, what you get may be different:</p><pre>{<br>  &quot;uuid&quot;: &quot;8790364716118035345&quot;,<br>  &quot;event&quot;: &quot;/restapi/v1.0/account/809646016/extension/62264425016/message-store&quot;,<br>  &quot;timestamp&quot;: &quot;2023-01-18T22:07:17.532Z&quot;,<br>  &quot;subscriptionId&quot;: &quot;2a4aafef-c9e0-4d9f-a34f-120ae5de8684&quot;,<br>  &quot;ownerId&quot;: &quot;62264425016&quot;,<br>  &quot;body&quot;: {<br>    &quot;accountId&quot;: 809646016,<br>    &quot;extensionId&quot;: 62264425016,<br>    &quot;lastUpdated&quot;: &quot;2023-01-18T22:07:11.635Z&quot;,<br>    &quot;changes&quot;: [<br>      {<br>        &quot;type&quot;: &quot;Pager&quot;,<br>        &quot;newCount&quot;: 2,<br>        &quot;updatedCount&quot;: 0,<br>        &quot;newMessageIds&quot;: [<br>          1890265271016,<br>          1890265273016<br>        ]<br>      }<br>    ]<br>  }<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1204ce5843b8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[RingCentral Chatbot Adaptive Cards]]></title>
            <link>https://medium.com/@tylerlong/ringcentral-chatbot-adaptive-cards-ebbbee62e727?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/ebbbee62e727</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 17:24:18 GMT</pubDate>
            <atom:updated>2022-12-16T17:24:18.824Z</atom:updated>
            <content:encoded><![CDATA[<p>RingCentral Chatbot Adaptive Cards was released quite a while ago, but I just got some time to try it out recently. Today I am going to share my experience about RingCentral Adaptive Cards. The following topics will be covered: firstly, how to bootstrap a RingCentral Chatbot project; secondly, how to post an adaptive card; lastly, how to show a dialog.</p><h4>How to bootstrap a RingCentral Chatbot project</h4><p>Please follow <a href="https://github.com/tylerlong/glip-ping-chatbot/tree/express">this guide</a>. If you want to deploy to AWS Lambda, follow <a href="https://github.com/tylerlong/glip-ping-chatbot/tree/lambda">this guide</a> instead.</p><p>I don’t want to post the details here because those two guides mentioned above are very comprehensive and they should be sufficient for you to get started.</p><p>Basically you need to create a Node.js project, create a RingCentral app, do some configuration, setup the database and test the sample code provided. And by the end of the day you should have a working chatbot which responds with a “pong” whenever it received a “ping”:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/341/1*yBwnDzpLp4_NJMi2f357TA.png" /></figure><p>And the business logic code is short and concise:</p><pre>if (type === &#39;Message4Bot&#39; &amp;&amp; text === &#39;ping&#39;) {<br>    await bot.sendMessage(group.id, { text: &#39;pong&#39; })<br>}</pre><h4>How to post an adaptive card</h4><p>First of all, what is an <a href="https://developers.ringcentral.com/guide/team-messaging/adaptive-cards">adaptive card</a>?</p><blockquote>Adaptive Cards allow users to interact directly with messages to get more work done without leaving RingCentral team messaging.</blockquote><blockquote>RingCentral implements the <a href="https://adaptivecards.io/">Adaptive Card framework</a> version 1.3. Originally developed by Microsoft, this open source framework aids developers with the design and development of richly formatted and interactive messages common to team chat and messaging services.</blockquote><p>Posting an adaptive card is very similar to posting a plain text message:</p><pre>await bot.sendAdaptiveCard(group.id, {<br>        type: &#39;AdaptiveCard&#39;,<br>        $schema: &#39;<a href="http://adaptivecards.io/schemas/adaptive-card.json&#39;">http://adaptivecards.io/schemas/adaptive-card.json&#39;</a>,<br>        version: &#39;1.3&#39;,<br>        body: [<br>          {<br>            type: &#39;ActionSet&#39;,<br>            actions: [<br>              {<br>                type: &#39;Action.Submit&#39;,<br>                title: &#39;Open dialog&#39;,<br>                data: {<br>                  path: &#39;open-dialog&#39;,<br>                },<br>              },<br>            ],<br>          },<br>        ],<br>      });</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ypMPhX6Iq-6QgQARSMaHCw.png" /></figure><p>Our adaptive card is a simple one. It simply displays a button with text “Open Dialog”. Let’s move on to the next section.</p><h4>How to show a dialog</h4><blockquote>Modal dialogs are windows that developers can cause to be opened and that float above a conversation. While a modal dialog is open, users cannot interact with any part of the team messaging interface. Dialogs can be forcibly closed by the end user, or be closed naturally following a successful interaction with a dialog’s contents.</blockquote><p>Please note that, by design you cannot show a dialog by invoking REST API. Instead, you can respond to user’s submit action. A submit action is when user clicks a button on an adaptive card.</p><pre>if (type === &#39;UserSubmit&#39;) {<br>    return {<br>      type: &#39;dialog&#39;,<br>      dialog: {<br>        title: &#39;Dialog&#39;,<br>        size: &#39;large&#39;,<br>        iconUrl:<br>          &#39;<a href="https://www.kindpng.com/picc/m/255-2554719_a-generic-square-placeholder-image-with-rounded-corners.png&#39;">https://www.kindpng.com/picc/m/255-2554719_a-generic-square-placeholder-image-with-rounded-corners.png&#39;</a>,<br>        // iframeUrl: &#39;<a href="https://www.ringcentral.com&#39;">https://www.ringcentral.com&#39;</a>,<br>        card: {<br>          type: &#39;AdaptiveCard&#39;,<br>          version: &#39;1.3&#39;,<br>          $schema: &#39;<a href="http://adaptivecards.io/schemas/adaptive-card.json&#39;">http://adaptivecards.io/schemas/adaptive-card.json&#39;</a>,<br>          body: [<br>            {<br>              type: &#39;TextBlock&#39;,<br>              text: &#39;Hello.&#39;,<br>              wrap: true,<br>              size: &#39;ExtraLarge&#39;,<br>              weight: &#39;Bolder&#39;,<br>            },<br>          ],<br>        },<br>      },<br>    };<br>  }</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L_X5jiCoAf0XNVbgm7FdoQ.png" /></figure><h4>Source code</h4><p>For a working demo, please refer to the full source code <a href="https://github.com/tylerlong/ringcentral-chatbot-dialog-demo/blob/main/src/express.ts">here</a>.</p><h4>Summary</h4><p>In this article we covered some details about RingCentral Chatbot Adaptive Cards. By following the steps mentioned in this article, you should be able to create a RingCentral Chatbot and display adaptive cards and dialogs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ebbbee62e727" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Host a RingCentral function on Microsoft Azure]]></title>
            <link>https://medium.com/@tylerlong/host-a-ringcentral-function-on-microsoft-azure-eeff85ee7375?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/eeff85ee7375</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 17:22:15 GMT</pubDate>
            <atom:updated>2022-12-16T17:22:15.603Z</atom:updated>
            <content:encoded><![CDATA[<p>Today we are going to do an interesting experiment: host a RingCentral function on Microsoft Azure. I use macOS as my development computer. For those who use Windows, the steps should be similar, but you’d have to reference some <a href="https://docs.microsoft.com/en-us/azure/?product=featured">official documents of Azure</a>.</p><h4><a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli">Install the Azure CLI</a></h4><pre>brew update<br>brew install azure-cli</pre><h4><a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=macos%2Ccsharp%2Cbash#install-the-azure-functions-core-tools">Install the Azure Functions Core Tools</a></h4><pre>brew tap azure/functions<br>brew install azure-functions-core-tools@3</pre><h4><a href="https://dotnet.microsoft.com/download">Install .Net Core 3.1</a></h4><p>Please <a href="https://dotnet.microsoft.com/download">download</a> and install version 3.1. At the time that I am writing this article, 3.1 is the active LTS version.</p><h4>Create a new Project</h4><pre>func init MyAwesomeProject --dotnet</pre><h4>Create a new function</h4><pre>func new --name RingCentralExample --template &quot;HTTP trigger&quot; --authlevel &quot;anonymous&quot;</pre><h4>Restore dependencies</h4><pre>dotnet restore</pre><h4>Run the function locally</h4><pre>fun start</pre><p>Then visit <a href="http://localhost:7071/api/RingCentralExample?name=Tyler">http://localhost:7071/api/RingCentralExample?name=Tyler</a> to test it. You should see something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JlQZuJyy-9bKXPYdZ3ZD0A.png" /></figure><h4>Install RingCentral.Net SDK</h4><pre>dotnet add package RingCentral.Net</pre><h4>Invoke RingCentral API</h4><p>Update our function and make it look like below:</p><pre>[FunctionName(&quot;RingCentralExample&quot;)]<br>public static async Task&lt;IActionResult&gt; Run(<br>    [HttpTrigger(AuthorizationLevel.Anonymous, &quot;get&quot;, &quot;post&quot;, Route = null)] HttpRequest req,<br>    ILogger log)<br>{<br>    log.LogInformation(&quot;C# HTTP trigger function processed a request.&quot;);</pre><pre>    var clientId = req.Query[&quot;clientId&quot;];<br>    var clientSecret = req.Query[&quot;clientSecret&quot;];<br>    var username = req.Query[&quot;username&quot;];<br>    var extension = req.Query[&quot;extension&quot;];<br>    var password = req.Query[&quot;password&quot;];<br>    <br>    var rc = new RestClient(clientId, clientSecret, true);<br>    try {<br>        await rc.Authorize(username, extension, password);<br>        var extInfo = await rc.Restapi().Account().Extension().Get();<br>        return new OkObjectResult($&quot;{DateTime.Now.ToString(&quot;yyyyMMdd HH:mm:ss ffff&quot;)}\n\nHello, {extInfo.contact.firstName} {extInfo.contact.lastName}.&quot;);<br>    } catch(RestException re) {<br>        return new OkObjectResult($&quot;{DateTime.Now.ToString(&quot;yyyyMMdd HH:mm:ss ffff&quot;)}\n\n{Utils.FormatHttpMessage(re.httpResponseMessage, re.httpRequestMessage)}&quot;);<br>    }<br>}</pre><p>We get 5 credential parameters from query string:</p><ul><li>clientId</li><li>clientSecret</li><li>username</li><li>extension</li><li>password</li></ul><p>And we use the credentials to do authorization and invoke RingCentral API:</p><pre>var rc = new RestClient(clientId, clientSecret, true);<br>await rc.Authorize(username, extension, password);<br>var extInfo = await rc.Restapi().Account().Extension().Get();</pre><p><a href="https://github.com/ringcentral/ringcentral.net">RingCentral.Net SDK</a> makes it very easy to use RingCentral API.</p><p>Then we return a greeting message to the current user:</p><pre>return new OkObjectResult($&quot;{DateTime.Now.ToString(&quot;yyyyMMdd HH:mm:ss ffff&quot;)}\n\nHello, {extInfo.contact.firstName} {extInfo.contact.lastName}.&quot;);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/912/1*YC9UnyS7dERUMC5OkWqaVA.png" /></figure><p>What if credentials are wrong? We handled the exception:</p><pre>} catch(RestException re) {<br>    return new OkObjectResult($&quot;{DateTime.Now.ToString(&quot;yyyyMMdd HH:mm:ss ffff&quot;)}\n\n{Utils.FormatHttpMessage(re.httpResponseMessage, re.httpRequestMessage)}&quot;);<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lB0mr5DZRtIiUjf9bgdhXw.png" /></figure><h4><strong>Create remote resources</strong></h4><p>Create resource group:</p><pre>az group create --name MyAwesomeProject-rg --location westus</pre><p>Create storage acccount:</p><pre>az storage account create --name myawesomeprojectsa --location westus --resource-group MyAwesomeProject-rg --sku Standard_LRS</pre><p>Create function</p><pre>az functionapp create --resource-group MyAwesomeProject-rg --consumption-plan-location westus --runtime dotnet --functions-version 3 --name my-awesome-project-rc-tyler-liu --storage-account myawesomeprojectsa</pre><h4>Publish the app</h4><pre>func azure functionapp publish my-awesome-project-rc-tyler-liu</pre><p>Then you will be able to test your deployment by visiting <a href="https://my-awesome-project-rc-tyler-liu.azurewebsites.net/api/ringcentralexample?clientId=clientID&amp;clientSecret=clientSecret&amp;username=username&amp;extension=extension&amp;password=password">https://my-awesome-project-rc-tyler-liu.azurewebsites.net/api/ringcentralexample?clientId=clientID&amp;clientSecret=clientSecret&amp;username=username&amp;extension=extension&amp;password=password</a></p><p>Of course you need to replace the query parameters with real credentials in order to get a greeting:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X5o5q79KSuRHXb7jrRJOqA.png" /></figure><h4>Clean up resources</h4><p>Because we are just doing an experiment for learning purpose, it doesn’t make sense to keep the resources we created after the experiment is successful. Let’s do the clean up work.</p><p>It could be done by one command since all the resources are under the same resource group:</p><pre>az group delete --name MyAwesomeProject-rg</pre><h4>Summary</h4><p>That’s it for today’s tutorial. It’s basically a step by step tutorial teaching readers how to create and host a RingCentral application on Microsoft Azure. It’s pretty straightforward. I have the feeling that Azure is easier to get started with than AWS. In the future I may use more Azure.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eeff85ee7375" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Publish a Java library to mavenCentral using Gradle 7]]></title>
            <link>https://medium.com/@tylerlong/publish-a-java-library-to-mavencentral-using-gradle-7-656630b9392c?source=rss-a5fa945f06b5------2</link>
            <guid isPermaLink="false">https://medium.com/p/656630b9392c</guid>
            <dc:creator><![CDATA[Tyler Liu]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 17:21:07 GMT</pubDate>
            <atom:updated>2022-12-16T17:21:07.593Z</atom:updated>
            <content:encoded><![CDATA[<p>This article is a sequel of <a href="https://medium.com/ringcentral-developers/publish-an-open-source-library-to-mavencentral-4c104652da8d">Publish an Open Source Library to mavenCentral</a>. In that article, we talked about how to publish an open source library to mavenCentral. As time goes, some of the content there becomes out-dated. Especially the way I shared in that article doesn’t work any more with latest Gradle 7.</p><h4>What remain the same?</h4><p>First of all, the same as last article, you need to have a Java library to publish, obviously. I have to say that most people have no library to publish. Because what they build are applications instead of libraries. But if you do have one, continue reading.</p><p>Secondly, you still need a public/secret key pair to publish a Java library. You need to create one if you don’t have one. The details are covered in article <a href="https://medium.com/ringcentral-developers/publish-an-open-source-library-to-mavencentral-4c104652da8d">Publish an Open Source Library to mavenCentral</a>.</p><h4>Gradle 7 &amp; maven-publish plugin</h4><p>We will use Grade 7, the latest version of Gradle. Last time we were using Gradle 6. Please refer to <a href="https://docs.gradle.org/7.0/userguide/upgrading_version_6.html">Upgrading your build from Gradle 6.x to the latest</a>. One notable breaking change is the maven plugin has been removed. You should use the maven-publish plugin instead.</p><p>So please change</p><pre>plugins {<br>    ...<br>    id &#39;maven&#39;<br>    ...<br>}</pre><p>to</p><pre>plugins {<br>    ...<br>    id &#39;maven-publish&#39;<br>    ...<br>}</pre><p>You also need to replace</p><pre>uploadArchives {<br>  repositories {<br>    mavenDeployer {<br>      beforeDeployment { MavenDeployment deployment -&gt; signing.signPom(deployment) }</pre><pre>repository(url: &quot;<a href="https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/">https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</a>&quot;) {<br>        if (project.hasProperty(&#39;ossrhUsername&#39;)) {<br>            authentication(userName: ossrhUsername, password: ossrhPassword)<br>        }<br>      }</pre><pre>snapshotRepository(url: &quot;<a href="https://s01.oss.sonatype.org/content/repositories/snapshots/">https://s01.oss.sonatype.org/content/repositories/snapshots/</a>&quot;) {<br>        if (project.hasProperty(&#39;ossrhUsername&#39;)) {<br>            authentication(userName: ossrhUsername, password: ossrhPassword)<br>        }<br>      }</pre><pre>pom.project {<br>        name &#39;RingCentral SDK&#39;<br>        packaging &#39;jar&#39;<br>        artifactId &#39;ringcentral&#39;<br>        description &#39;RingCentral Java SDK&#39;<br>        url &#39;<a href="https://github.com/ringcentral/ringcentral-java&#39;">https://github.com/ringcentral/ringcentral-java&#39;</a><br>        licenses {<br>            license {<br>                name &quot;MIT&quot;<br>                url &quot;<a href="https://opensource.org/licenses/MIT">https://opensource.org/licenses/MIT</a>&quot;<br>            }<br>        }<br>        developers {<br>            developer {<br>                id &#39;tylerliu&#39;<br>                name &#39;Tyler Liu&#39;<br>                email &#39;<a href="mailto:tyler.liu@ringcentral.com">tyler.liu@ringcentral.com</a>&#39;<br>            }<br>        }<br>        scm {<br>            connection &#39;scm:git:git://<a href="mailto:git@github.com">git@github.com</a>:ringcentral/ringcentral-java.git&#39;<br>            developerConnection &#39;scm:git:ssh://<a href="mailto:git@github.com">git@github.com</a>:ringcentral/ringcentral-java.git&#39;<br>            url &#39;<a href="https://github.com/ringcentral/ringcentral-java&#39;">https://github.com/ringcentral/ringcentral-java&#39;</a><br>        }<br>      }<br>    }<br>  }<br>}</pre><p>with</p><pre>publishing {<br>    publications {<br>        mavenJava(MavenPublication) {<br>            artifactId = &#39;ringcentral&#39;<br>            from components.java<br>            versionMapping {<br>                usage(&#39;java-api&#39;) {<br>                    fromResolutionOf(&#39;runtimeClasspath&#39;)<br>                }<br>                usage(&#39;java-runtime&#39;) {<br>                    fromResolutionResult()<br>                }<br>            }<br>            pom {<br>                name = &#39;RingCentral SDK&#39;<br>                description = &#39;RingCentral Java SDK&#39;<br>                url = &#39;<a href="https://github.com/ringcentral/ringcentral-java&#39;">https://github.com/ringcentral/ringcentral-java&#39;</a><br>                licenses {<br>                    license {<br>                        name = &#39;MIT&#39;<br>                        url = &#39;<a href="https://opensource.org/licenses/MIT&#39;">https://opensource.org/licenses/MIT&#39;</a><br>                    }<br>                }<br>                developers {<br>                    developer {<br>                        id = &#39;tylerliu&#39;<br>                        name = &#39;Tyler Liu&#39;<br>                        email = &#39;<a href="mailto:tyler.liu@ringcentral.com">tyler.liu@ringcentral.com</a>&#39;<br>                    }<br>                }<br>                scm {<br>                    connection = &#39;scm:git:git://<a href="mailto:git@github.com">git@github.com</a>:ringcentral/ringcentral-java.git&#39;<br>                    developerConnection = &#39;scm:git:ssh://<a href="mailto:git@github.com">git@github.com</a>:ringcentral/ringcentral-java.git&#39;<br>                    url = &#39;<a href="https://github.com/ringcentral/ringcentral-java&#39;">https://github.com/ringcentral/ringcentral-java&#39;</a><br>                }<br>            }<br>        }<br>    }<br>    repositories {<br>        maven {<br>            url = &quot;<a href="https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/">https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</a>&quot;<br>            credentials {<br>                username = ossrhUsername<br>                password = ossrhPassword<br>            }<br>        }<br>    }<br>}</pre><p>Please note that, I am using the <a href="https://github.com/ringcentral/ringcentral-java">RingCentral Java SDK</a> library as example. Please adjust the configuration based on your library’s details.</p><h4>Code Signing</h4><p>Code sign part is also quite different from before. Previously, we have the following configuration for code signing:</p><pre>task javadocJar(type: Jar) {<br>    classifier = &#39;javadoc&#39;<br>    from javadoc<br>}<br>task sourcesJar(type: Jar) {<br>    classifier = &#39;sources&#39;<br>    from sourceSets.main.allSource<br>}<br>artifacts {<br>    archives javadocJar, sourcesJar<br>}<br>signing {<br>    sign configurations.archives<br>}</pre><p>Now it needs to be updated to:</p><pre>signing {<br>    sign publishing.publications.mavenJava<br>}</pre><p>It is way simpler, I have to say. So Gradle 7 does have some improvements compared to Gradle 6.</p><h4>Command to publish</h4><p>Previously, we use a customized command named ./gradlew uploadArchives , now we use the built-in command ./gradlew publish .</p><h4>Summary</h4><p>OK, that concludes this article. It’s not a brand new article. It’s a sequel of <a href="https://medium.com/ringcentral-developers/publish-an-open-source-library-to-mavencentral-4c104652da8d">Publish an Open Source Library to mavenCentral</a>. It is for those who wants to stay update-to-date with the latest Gradle tool.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=656630b9392c" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>