JS empowerment with native platform APIs
As friends of the NativeScript community and preferred partners, we’re constantly trying to improve the NativeScript framework, community, and developer experience.
Sometimes it’s good to reiterate NativeScript’s goal: to empower JavaScript with native platform APIs. This has been the core focus for a while, and contrary to a common perspective among developers, NativeScript itself is actually not a competitor to other frameworks. Rather it is designed fundamentally to celebrate platforms while enhancing the entire JavaScript community (including other approaches, like React Native or Capacitor). As an example, @nativescript/capacitor allows you to use the full NativeScript capabilities within Capacitor itself.
@nativescript/core is a JavaScript library that takes full advantage of what NativeScript offers (it’s merely just a glimpse of what is possible with NativeScript as a whole), and all the other flavors like @nativescript/angular, nativescript-vue, and react-nativescript are compliments to each framework itself.
WebSocket plugin for NativeScript
WebSockets are frequently required for applications, be it for standard WebSocket communication, MQTT, debugging through Redux Remote Devtools, using socket.io, and sometimes even used internally on NativeScript itself.
When researching the best solutions for a WebSocket polyfill, there were no implementations better and more battle-tested than React Native’s. With that in mind, we decided to use its approach with NativeScript. Something we later discovered to be surprisingly easy and painless due to the overall quality of the original implementation. There were only three functions that were not needed (logs and assertion helpers from the original implementation), but we reimplemented them to maintain a similar behavior. In the end, it proves that React Native and NativeScript are really complementary and quite interoperable.
Visit npm to use the WebSocket plugin for NativeScript, and scroll down to proceed with the Implementation part!
Implementation
Check the original React Native implementation from where we started. Except for a few functions, like RCTAssert, RCTAssertParam, and RCTLog, everything else just worked straight out of the box. Since NativeScript allows you to use platform APIs as they were designed (most often synchronously) and without the need for a bridge, we also could drop all related bridge code from the React Native implementation and adjust a few data handling items (eg. converting from NSData to ArrayBuffer).
Here are the steps we followed:
Created a public workspace for NativeScript plugins based on the NativeScript plugin workspace docs and generated the plugin through Nx.
Created a native-src with the Xcode project and built scripts, using the React Native approach.
Ran
ns typings ios
from within the "apps/demo" folder to generate the typings for the RCTSRWebSocket class.Started writing the WebSocket polyfill straight from JavaScript, which involved creating the Native delegate and calling a callback when these events happened.
Made small adjustments related to data types and converted them to JavaScript types when needed.
And the remaining implementations were just a case of translating Objective-C to TypeScript:
One critical detail to remember about JavaScript while handling any platform API is that it’s single-threaded. Blocking calls will block the main thread, which is the reason we needed a native implementation on iOS. The iOS native implementation is responsible for receiving the calls, doing work on other threads, and then returning back to the main thread.
On the Android side, React Native uses OkHttp for its WebSockets, which already handles all the threading details, so we needed no native code at all! All we did here was translate the Java implementation to JavaScript. However, we could have used the .java "as-is" as well, which is where NativeScript is endlessly versatile (you have the choice), so we just decided to use TypeScript here.
Another interesting detail with NativeScript is the sheer reduction in code to manage - originally this code was over 400 lines: WebSocket module. And we were able to transform this into under 150 lines of easy to read JavaScript code. Below you can see a comparison between the original code for WebSocket module in Java and in NativeScript:
A couple of conversions were needed for binary data. In React Native, binary data is converted to a base64 string in JavaScript and converted back to binary on send:
In NativeScript, we are able to manipulate native types directly, so we can do a simple conversion:
The same adjustments made to convert NSData to ArrayBuffer were needed for Android, but since Android doesn’t have a helper for it, we converted from ByteString:
And that’s it! We now have a nice API for WebSockets for both iOS and Android platforms with a well defined interface:
Then it was just a matter of wiring up a simple WebSocket polyfill.
Not to compete, but evolve and excel
In conclusion, we want to underline that it is part of our mission as a company and engineers individually to contribute to open-source technologies and make them even better. This way, we can provide developers from around the world with more flexible and responsible options to build software products and overcome their daily challenges. Also, NativeScript is in no way competing with other JS frameworks and on the contrary can be applied in combination with other technologies, for example inside Capacitor by Ionic.
Finally, we admire the quality with which WebSockets polyfill was implemented in React Native. The code structure with minimum dependencies allowed us to use it with NativeScript quite effortlessly.