From 72db5faef44e586c0d9fcc4752f13d17caba46cf Mon Sep 17 00:00:00 2001 From: Rodrigo Martin Date: Fri, 12 Mar 2021 12:59:08 +0100 Subject: [PATCH] feat: Export Socket class (#104) --- README.md | 27 +++ __tests__/index.test.js | 12 +- .../react/tcpsocket/TcpSocketModule.java | 2 - examples/tcpsockets/.prettierrc | 10 + examples/tcpsockets/.prettierrc.js | 6 - examples/tcpsockets/App.js | 87 +++----- examples/tcpsockets/declaration.d.ts | 4 + examples/tcpsockets/examples/README.md | 15 ++ examples/tcpsockets/examples/echo.js | 35 ++++ examples/tcpsockets/examples/main.js | 44 ++++ examples/tcpsockets/ios/Podfile.lock | 8 +- .../ios/tcpsockets.xcodeproj/project.pbxproj | 38 ++-- examples/tcpsockets/package.json | 9 +- examples/tcpsockets/tsconfig.json | 17 ++ examples/tcpsockets/yarn.lock | 5 + ios/TcpSocketClient.m | 2 + lib/types/TcpSocket.d.ts | 189 ------------------ react-native-tcp-socket.podspec | 2 +- src/Globals.js | 4 +- src/Server.js | 49 ++--- src/{TcpSocket.js => Socket.js} | 53 ++--- src/index.js | 12 +- tsconfig.json | 1 + 23 files changed, 282 insertions(+), 349 deletions(-) create mode 100644 examples/tcpsockets/.prettierrc delete mode 100644 examples/tcpsockets/.prettierrc.js create mode 100644 examples/tcpsockets/declaration.d.ts create mode 100644 examples/tcpsockets/examples/README.md create mode 100644 examples/tcpsockets/examples/echo.js create mode 100644 examples/tcpsockets/examples/main.js create mode 100644 examples/tcpsockets/tsconfig.json delete mode 100644 lib/types/TcpSocket.d.ts rename src/{TcpSocket.js => Socket.js} (96%) diff --git a/README.md b/README.md index d0b4618..274f1af 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ React Native TCP socket API for Android & iOS with **client SSL/TLS support**. I ## Table of Contents - [Getting started](#getting-started) + - [Overriding `net`](#overriding-net) - [Using React Native >= 0.60](#using-react-native--060) - [Self-Signed SSL (only available for React Native > 0.60)](#self-signed-ssl-only-available-for-react-native--060) - [Using React Native < 0.60](#using-react-native--060-1) @@ -40,6 +41,32 @@ or npm: npm install --save react-native-tcp-socket ``` +#### Overriding `net` +Since `react-native-tcp-socket` offers the same API as Node's net, in case you want to import this module as `net` or use `require('net')` in your JavaScript, you must add the following lines to your `package.json` file. + +```json +{ + "react-native": { + "net": "react-native-tcp-socket" + } +} +``` + +In addition, in order to obtain the TS types (or autocompletion) provided by this module, you must also add the following to your custom declarations file. + +```ts +... +declare module 'net' { + import TcpSockets from 'react-native-tcp-socket'; + export = TcpSockets; +} +``` + +If you want to avoid duplicated `net` types, make sure not to use the default `node_modules/@types` in your `tsconfig.json` `"typeRoots"` property. + +_Check the [example app](./examples/tcpsockets/) provided for a working example._ + + #### Using React Native >= 0.60 Linking the package manually is not required anymore with [Autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md). diff --git a/__tests__/index.test.js b/__tests__/index.test.js index e2380fa..d896b00 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -1,7 +1,5 @@ import { expect, test } from '@jest/globals'; -import TcpSockets from '../src/index'; -import TcpServer from '../src/Server'; -import TcpSocket from '../src/TcpSocket'; +import net from '../src/index'; test('create-client', () => { const options = { @@ -13,11 +11,11 @@ test('create-client', () => { // interface: "wifi" }; - const socket = TcpSockets.createConnection(options, () => {}); - expect(socket).toBeInstanceOf(TcpSocket); + const socket = net.createConnection(options, () => {}); + expect(socket).toBeInstanceOf(net.Socket); }); test('create-server', () => { - const server = TcpSockets.createServer(() => {}); - expect(server).toBeInstanceOf(TcpServer); + const server = net.createServer(() => {}); + expect(server).toBeInstanceOf(net.Server); }); diff --git a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java index 341e470..93fa384 100644 --- a/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java +++ b/android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java @@ -161,8 +161,6 @@ public void run() { try { TcpSocketServer server = new TcpSocketServer(socketMap, TcpSocketModule.this, cId, options); socketMap.put(cId, server); - int port = server.getListeningPort(); - String host = options.getString("host"); onListen(cId, server); } catch (Exception uhe) { onError(cId, uhe.getMessage()); diff --git a/examples/tcpsockets/.prettierrc b/examples/tcpsockets/.prettierrc new file mode 100644 index 0000000..5317d1a --- /dev/null +++ b/examples/tcpsockets/.prettierrc @@ -0,0 +1,10 @@ +{ + "tabWidth": 4, + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "bracketSpacing": true, + "printWidth": 100, + "jsxBracketSameLine": true, + "arrowParens": "always" +} \ No newline at end of file diff --git a/examples/tcpsockets/.prettierrc.js b/examples/tcpsockets/.prettierrc.js deleted file mode 100644 index 5c4de1a..0000000 --- a/examples/tcpsockets/.prettierrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - bracketSpacing: false, - jsxBracketSameLine: true, - singleQuote: true, - trailingComma: 'all', -}; diff --git a/examples/tcpsockets/App.js b/examples/tcpsockets/App.js index c3777f4..f5aa808 100644 --- a/examples/tcpsockets/App.js +++ b/examples/tcpsockets/App.js @@ -5,16 +5,14 @@ */ import React from 'react'; -import { - ScrollView, - StyleSheet, - Text, - View -} from 'react-native'; +import { ScrollView, StyleSheet, Text, View } from 'react-native'; -import TcpSocket from 'react-native-tcp-socket'; +import { init, server, client } from './examples/echo'; class App extends React.Component { + /** + * @param {any} props + */ constructor(props) { super(props); @@ -22,94 +20,59 @@ class App extends React.Component { this.state = { chatter: [] }; } + /** + * @param {string | Error} msg + */ updateChatter(msg) { this.setState({ - chatter: this.state.chatter.concat([msg]) + // @ts-ignore + chatter: this.state.chatter.concat([msg]), }); } componentDidMount() { - const serverPort = Number(9 + (Math.random()*999).toFixed(0)); - const serverHost = "0.0.0.0"; - let server; - let client; - server = TcpSocket.createServer((socket) => { - this.updateChatter('client connected to server on ' + JSON.stringify(socket.address())); - console.log( - 'Server client', - socket.localAddress, - socket.localPort, - socket.remoteAddress, - socket.remotePort, - socket.remoteFamily - ); + server.on('connection', (socket) => { + this.updateChatter('Client connected to server on ' + JSON.stringify(socket.address())); socket.on('data', (data) => { - this.updateChatter('Server Received: ' + data); - socket.write('Echo server\r\n'); + this.updateChatter('Server client received: ' + data); }); socket.on('error', (error) => { - this.updateChatter('server client error ' + error); + this.updateChatter('Server client error ' + error); }); socket.on('close', (error) => { - this.updateChatter('server client closed ' + (error ? error : '')); + this.updateChatter('Server client closed ' + (error ? error : '')); }); - }).listen({port: serverPort, host: serverHost, reuseAddress: true}, () => { - this.updateChatter('opened server on ' + JSON.stringify(server.address())); }); server.on('error', (error) => { + this.updateChatter(error); this.updateChatter('Server error ' + error); }); server.on('close', () => { - this.updateChatter('server close'); + this.updateChatter('Server closed'); }); - client = TcpSocket.createConnection({ - port: serverPort, - host: serverHost, - localAddress: "127.0.0.1", - reuseAddress: true, - // localPort: 20000, - // interface: "wifi", - // tls: true - }, () => { - this.updateChatter('opened client on ' + JSON.stringify(client.address())); - client.write('Hello, server! Love, Client.'); + client.on('connect', () => { + this.updateChatter('Opened client on ' + JSON.stringify(client.address())); }); client.on('data', (data) => { - console.log( - 'Initial client', - client.localAddress, - client.localPort, - client.remoteAddress, - client.remotePort, - client.remoteFamily - ); - this.updateChatter('Client Received: ' + data); - this.client.destroy(); // kill client after server's response - this.server.close(); + this.updateChatter('Client received: ' + data); }); client.on('error', (error) => { - this.updateChatter('client error ' + error); + this.updateChatter('Client error ' + error); }); - client.on('close', () => { - this.updateChatter('client close'); + client.on('close', (error) => { + this.updateChatter('Client closed ' + (error ? error : '')); }); - this.server = server; - this.client = client; - } - - componentWillUnmount() { - this.server = null; - this.client = null; + init(); } render() { @@ -127,7 +90,7 @@ class App extends React.Component { ); } -}; +} const styles = StyleSheet.create({ container: { diff --git a/examples/tcpsockets/declaration.d.ts b/examples/tcpsockets/declaration.d.ts new file mode 100644 index 0000000..d3f5a4c --- /dev/null +++ b/examples/tcpsockets/declaration.d.ts @@ -0,0 +1,4 @@ +declare module 'net' { + import TcpSockets from 'react-native-tcp-socket'; + export = TcpSockets; +} diff --git a/examples/tcpsockets/examples/README.md b/examples/tcpsockets/examples/README.md new file mode 100644 index 0000000..e83c0b5 --- /dev/null +++ b/examples/tcpsockets/examples/README.md @@ -0,0 +1,15 @@ +# Cross-Platform examples + +In this folder, you can find a variety of examples to help you get started in using `react-native-tcp-socket`. Every example has a specific purpose and **is compatible** with Node.js. + +In order to run an example, you may import the `init`, `server` and clients from the example file and run it either from React Native ([`App.js`](../App.js)) or Node.js ([`main.js`](main.js)). + +Let us know if you find any issues. If you want to contribute or add a new example, feel free to submit a PR! + +## Table of Contents +- [Echo server](#echo-server) + + +### [Echo server](echo.js) + +An echo server just reflects a message received from a client to the same client. If we send a message saying "Hello, Server!", we will receive the same message, just like an echo. This example shows some basic TCP server and client interactions. \ No newline at end of file diff --git a/examples/tcpsockets/examples/echo.js b/examples/tcpsockets/examples/echo.js new file mode 100644 index 0000000..2d61064 --- /dev/null +++ b/examples/tcpsockets/examples/echo.js @@ -0,0 +1,35 @@ +const net = require('net'); +const PORT = Number(9 + (Math.random() * 999).toFixed(0)); + +const server = new net.Server(); +const client = new net.Socket(); + +function init() { + server.on('connection', (socket) => { + socket.write('Echo server\r\n'); + }); + + server.listen({ port: PORT, host: '127.0.0.1', reuseAddress: true }); + + client.connect( + // @ts-ignore + { + port: PORT, + host: '127.0.0.1', + localAddress: '127.0.0.1', + reuseAddress: true, + // localPort: 20000, + // interface: "wifi", + // tls: true + }, + () => { + client.write('Hello, server! Love, Client.'); + } + ); + + client.on('data', () => { + client.destroy(); // kill client after server's response + }); +} + +module.exports = { init, server, client }; diff --git a/examples/tcpsockets/examples/main.js b/examples/tcpsockets/examples/main.js new file mode 100644 index 0000000..5609389 --- /dev/null +++ b/examples/tcpsockets/examples/main.js @@ -0,0 +1,44 @@ +// Execute this file using NodeJS +const { init, server, client } = require('./echo'); + +server.on('connection', (socket) => { + console.log('Client connected to server on ' + JSON.stringify(socket.address())); + + socket.on('data', (data) => { + console.log('Server client received: ' + data); + }); + + socket.on('error', (error) => { + console.log('Server client error ' + error); + }); + + socket.on('close', (error) => { + console.log('Server client closed ' + (error ? error : '')); + }); +}); + +server.on('error', (error) => { + console.log('Server error ' + error); +}); + +server.on('close', () => { + console.log('Server closed'); +}); + +client.on('connect', () => { + console.log('Opened client on ' + JSON.stringify(client.address())); +}); + +client.on('data', (data) => { + console.log('Client received: ' + data); +}); + +client.on('error', (error) => { + console.log('Client error ' + error); +}); + +client.on('close', (error) => { + console.log('Client closed ' + (error ? error : '')); +}); + +init(); \ No newline at end of file diff --git a/examples/tcpsockets/ios/Podfile.lock b/examples/tcpsockets/ios/Podfile.lock index 5ce89fd..3942a33 100644 --- a/examples/tcpsockets/ios/Podfile.lock +++ b/examples/tcpsockets/ios/Podfile.lock @@ -186,7 +186,7 @@ PODS: - React-cxxreact (= 0.63.2) - React-jsi (= 0.63.2) - React-jsinspector (0.63.2) - - react-native-tcp-socket (4.5.5): + - react-native-tcp-socket (5.1.0): - CocoaAsyncSocket - React-Core - React-RCTActionSheet (0.63.2): @@ -269,7 +269,7 @@ DEPENDENCIES: - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - - react-native-tcp-socket (from `../node_modules/react-native-tcp-socket`) + - react-native-tcp-socket (from `../../..`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) @@ -319,7 +319,7 @@ EXTERNAL SOURCES: React-jsinspector: :path: "../node_modules/react-native/ReactCommon/jsinspector" react-native-tcp-socket: - :path: "../node_modules/react-native-tcp-socket" + :path: "../../.." React-RCTActionSheet: :path: "../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: @@ -361,7 +361,7 @@ SPEC CHECKSUMS: React-jsi: 54245e1d5f4b690dec614a73a3795964eeef13a8 React-jsiexecutor: 8ca588cc921e70590820ce72b8789b02c67cce38 React-jsinspector: b14e62ebe7a66e9231e9581279909f2fc3db6606 - react-native-tcp-socket: 6b4108c9000d0ad48f5ea89d3c971fb2e73da6ca + react-native-tcp-socket: 334094926111f66bd6b305859aac4d07745b8d81 React-RCTActionSheet: 910163b6b09685a35c4ebbc52b66d1bfbbe39fc5 React-RCTAnimation: 9a883bbe1e9d2e158d4fb53765ed64c8dc2200c6 React-RCTBlob: 39cf0ece1927996c4466510e25d2105f67010e13 diff --git a/examples/tcpsockets/ios/tcpsockets.xcodeproj/project.pbxproj b/examples/tcpsockets/ios/tcpsockets.xcodeproj/project.pbxproj index 4201a25..14d2b6d 100644 --- a/examples/tcpsockets/ios/tcpsockets.xcodeproj/project.pbxproj +++ b/examples/tcpsockets/ios/tcpsockets.xcodeproj/project.pbxproj @@ -209,7 +209,7 @@ 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - 42BB03B5C30DA5C32BAB0D9B /* [CP] Copy Pods Resources */, + 436D57CB9EEDC5E934EAA6EA /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -231,7 +231,7 @@ 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - B6950979BAA9507750EB8DD4 /* [CP] Copy Pods Resources */, + DEF91BEE2646397FE89B058D /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -435,7 +435,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 42BB03B5C30DA5C32BAB0D9B /* [CP] Copy Pods Resources */ = { + 436D57CB9EEDC5E934EAA6EA /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -475,44 +475,44 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B6950979BAA9507750EB8DD4 /* [CP] Copy Pods Resources */ = { + B70CC43002ED3D5D1E460CEA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-tcpsockets/Pods-tcpsockets-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Copy Pods Resources"; outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", + "$(DERIVED_FILE_DIR)/Pods-tcpsockets-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-tcpsockets/Pods-tcpsockets-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B70CC43002ED3D5D1E460CEA /* [CP] Check Pods Manifest.lock */ = { + DEF91BEE2646397FE89B058D /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-tcpsockets/Pods-tcpsockets-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle", ); + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-tcpsockets-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-tcpsockets/Pods-tcpsockets-resources.sh\"\n"; showEnvVarsInLog = 0; }; FD10A7F022414F080027D42C /* Start Packager */ = { diff --git a/examples/tcpsockets/package.json b/examples/tcpsockets/package.json index b2d4560..07045c0 100644 --- a/examples/tcpsockets/package.json +++ b/examples/tcpsockets/package.json @@ -9,7 +9,8 @@ "start": "react-native start", "test": "jest", "clean": "cd android && ./gradlew clean", - "lint": "eslint ." + "lint": "eslint .", + "checkjs": "tsc" }, "dependencies": { "react": "16.13.1", @@ -25,9 +26,13 @@ "eslint": "^6.5.1", "jest": "^25.1.0", "metro-react-native-babel-preset": "^0.59.0", - "react-test-renderer": "16.13.1" + "react-test-renderer": "16.13.1", + "typescript": "^4.2.3" }, "jest": { "preset": "react-native" + }, + "react-native": { + "net": "react-native-tcp-socket" } } diff --git a/examples/tcpsockets/tsconfig.json b/examples/tcpsockets/tsconfig.json new file mode 100644 index 0000000..b650409 --- /dev/null +++ b/examples/tcpsockets/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react", + "allowJs": true, + "lib": [ + "es2015" + ], + "checkJs": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "baseUrl": ".", + "typeRoots": [] + }, + "include": ["declaration.d.ts", "App.js", "examples"], +} \ No newline at end of file diff --git a/examples/tcpsockets/yarn.lock b/examples/tcpsockets/yarn.lock index 55b39db..ffd7d8f 100644 --- a/examples/tcpsockets/yarn.lock +++ b/examples/tcpsockets/yarn.lock @@ -6599,6 +6599,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3" + integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw== + ua-parser-js@^0.7.18: version "0.7.21" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" diff --git a/ios/TcpSocketClient.m b/ios/TcpSocketClient.m index 6c69aec..a3d9b15 100644 --- a/ios/TcpSocketClient.m +++ b/ios/TcpSocketClient.m @@ -51,6 +51,8 @@ - (id)initWithClientId:(NSNumber *)clientID andConfig:(id) _lock = [[NSLock alloc] init]; _tcpSocket = tcpSocket; [_tcpSocket setUserData: clientID]; + [_tcpSocket setDelegate: self]; + [_tcpSocket setDelegateQueue: [self methodQueue]]; } return self; diff --git a/lib/types/TcpSocket.d.ts b/lib/types/TcpSocket.d.ts deleted file mode 100644 index 03483c1..0000000 --- a/lib/types/TcpSocket.d.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * @typedef {"ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"} BufferEncoding - * - * @typedef {import('react-native').NativeEventEmitter} NativeEventEmitter - * - * @typedef {{address: string, family: string, port: number}} AddressInfo - * - * @typedef {{localAddress: string, localPort: number, remoteAddress: string, remotePort: number, remoteFamily: string}} NativeConnectionInfo - * - * @typedef {{ - * port: number; - * host?: string; - * timeout?: number, - * localAddress?: string, - * localPort?: number, - * interface?: 'wifi' | 'cellular' | 'ethernet', - * reuseAddress?: boolean, - * tls?: boolean, - * tlsCheckValidity?: boolean, - * tlsCert?: any, - * }} ConnectionOptions - * - * @extends {EventEmitter<'connect' | 'timeout' | 'data' | 'error' | 'close', any>} - */ -export default class TcpSocket extends EventEmitter<"connect" | "timeout" | "data" | "error" | "close", any> { - /** - * Initialices a TcpSocket. - * - * @param {number} id - * @param {NativeEventEmitter} eventEmitter - * @param {NativeConnectionInfo} [connectionInfo] - */ - constructor(id: number, eventEmitter: NativeEventEmitter, connectionInfo?: NativeConnectionInfo | undefined); - /** @private */ - private _id; - /** @private */ - private _eventEmitter; - /** @type {number} @private */ - private _timeoutMsecs; - /** @private */ - private _timeout; - /** @type {number} @private */ - private _state; - /** @private */ - private _encoding; - localAddress: string | undefined; - localPort: number | undefined; - remoteAddress: string | undefined; - remotePort: number | undefined; - remoteFamily: string | undefined; - /** - * @param {ConnectionOptions} options - * @param {() => void} [callback] - */ - connect(options: ConnectionOptions, callback?: (() => void) | undefined): TcpSocket; - _destroyed: boolean | undefined; - /** - * Sets the socket to timeout after `timeout` milliseconds of inactivity on the socket. By default `TcpSocket` do not have a timeout. - * - * When an idle timeout is triggered the socket will receive a `'timeout'` event but the connection will not be severed. - * The user must manually call `socket.end()` or `socket.destroy()` to end the connection. - * - * If `timeout` is 0, then the existing idle timeout is disabled. - * - * The optional `callback` parameter will be added as a one-time listener for the `'timeout'` event. - * - * @param {number} timeout - * @param {() => void} [callback] - */ - setTimeout(timeout: number, callback?: (() => void) | undefined): TcpSocket; - /** - * @private - * @param {number} [timeout] - */ - private _activateTimer; - /** - * @private - */ - private _clearTimeout; - /** - * Set the encoding for the socket as a Readable Stream. By default, no encoding is assigned and stream data will be returned as `Buffer` objects. - * Setting an encoding causes the stream data to be returned as strings of the specified encoding rather than as Buffer objects. - * - * For instance, calling `socket.setEncoding('utf8')` will cause the output data to be interpreted as UTF-8 data, and passed as strings. - * Calling `socket.setEncoding('hex')` will cause the data to be encoded in hexadecimal string format. - * - * @param {BufferEncoding} [encoding] - */ - setEncoding(encoding?: "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex" | undefined): TcpSocket; - /** - * Enable/disable the use of Nagle's algorithm. When a TCP connection is created, it will have Nagle's algorithm enabled. - * - * Nagle's algorithm delays data before it is sent via the network. It attempts to optimize throughput at the expense of latency. - * - * Passing `true` for `noDelay` or not passing an argument will disable Nagle's algorithm for the socket. Passing false for noDelay will enable Nagle's algorithm. - * - * @param {boolean} noDelay Default: `true` - */ - setNoDelay(noDelay?: boolean): TcpSocket; - /** - * Enable/disable keep-alive functionality, and optionally set the initial delay before the first keepalive probe is sent on an idle socket. - * - * `initialDelay` is ignored. - * - * @param {boolean} enable Default: `false` - * @param {number} initialDelay ***IGNORED**. Default: `0` - */ - setKeepAlive(enable?: boolean, initialDelay?: number): TcpSocket; - /** - * Returns the bound `address`, the address `family` name and `port` of the socket as reported - * by the operating system: `{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`. - * - * @returns {AddressInfo | {}} - */ - address(): AddressInfo | {}; - /** - * @param {string | Buffer | Uint8Array} data - * @param {BufferEncoding} [encoding] - */ - end(data: string | Buffer | Uint8Array, encoding?: "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex" | undefined): void; - destroy(): void; - /** - * Sends data on the socket. The second parameter specifies the encoding in the case of a string — it defaults to UTF8 encoding. - * - * The optional callback parameter will be executed when the data is finally written out, which may not be immediately. - * - * @param {string | Buffer | Uint8Array} buffer - * @param {BufferEncoding} [encoding] - * @param {(error: string | null) => void} [callback] - */ - write(buffer: string | Buffer | Uint8Array, encoding?: "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex" | undefined, callback?: ((error: string | null) => void) | undefined): void; - ref(): void; - unref(): void; - /** - * @private - */ - private _registerEvents; - _dataListener: import("react-native").EmitterSubscription | undefined; - _errorListener: import("react-native").EmitterSubscription | undefined; - _closeListener: import("react-native").EmitterSubscription | undefined; - _connectListener: import("react-native").EmitterSubscription | undefined; - /** - * @private - */ - private _unregisterEvents; - /** - * @private - * @param {string | Buffer | Uint8Array} buffer - * @param {BufferEncoding} [encoding] - */ - private _generateSendBuffer; - /** - * @private - * @param {NativeConnectionInfo} connectionInfo - */ - private _setConnected; - /** - * @private - */ - private _setDisconnected; -} -export type BufferEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex"; -export type NativeEventEmitter = import("react-native").NativeEventEmitter; -export type AddressInfo = { - address: string; - family: string; - port: number; -}; -export type NativeConnectionInfo = { - localAddress: string; - localPort: number; - remoteAddress: string; - remotePort: number; - remoteFamily: string; -}; -export type ConnectionOptions = { - port: number; - host?: string | undefined; - timeout?: number | undefined; - localAddress?: string | undefined; - localPort?: number | undefined; - interface?: "wifi" | "cellular" | "ethernet" | undefined; - reuseAddress?: boolean | undefined; - tls?: boolean | undefined; - tlsCheckValidity?: boolean | undefined; - tlsCert?: any; -}; -import EventEmitter from "eventemitter3"; -import { Buffer } from "buffer"; diff --git a/react-native-tcp-socket.podspec b/react-native-tcp-socket.podspec index 7b14fac..ed7e37b 100644 --- a/react-native-tcp-socket.podspec +++ b/react-native-tcp-socket.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.platforms = { :ios => "9.0", :tvos => "10.0", :osx => "10.14" } s.source = { :git => "https://github.com/Rapsssito/react-native-tcp-socket.git", :tag => "#v{s.version}" } - s.source_files = "ios/**/*.{h,m,swift}" + s.source_files = "ios/**.{h,m,swift}" s.requires_arc = true s.dependency "React-Core" diff --git a/src/Globals.js b/src/Globals.js index b80fcb4..b12410b 100644 --- a/src/Globals.js +++ b/src/Globals.js @@ -3,10 +3,10 @@ const Sockets = NativeModules.TcpSockets; let instanceNumber = 0; -function getInstanceNumber() { +function getNextId() { return instanceNumber++; } const nativeEventEmitter = new NativeEventEmitter(Sockets); -export { nativeEventEmitter, getInstanceNumber }; +export { nativeEventEmitter, getNextId }; diff --git a/src/Server.js b/src/Server.js index 684cce8..89cd608 100644 --- a/src/Server.js +++ b/src/Server.js @@ -3,24 +3,24 @@ import { NativeModules } from 'react-native'; import EventEmitter from 'eventemitter3'; const Sockets = NativeModules.TcpSockets; -import TcpSocket from './TcpSocket'; -import { nativeEventEmitter, getInstanceNumber } from './Globals'; +import Socket from './Socket'; +import { nativeEventEmitter, getNextId } from './Globals'; /** * @extends {EventEmitter<'connection' | 'listening' | 'error' | 'close', any>} */ export default class Server extends EventEmitter { /** - * @param {(socket: TcpSocket) => void} [connectionCallback] Automatically set as a listener for the `'connection'` event. + * @param {(socket: Socket) => void} [connectionCallback] Automatically set as a listener for the `'connection'` event. */ constructor(connectionCallback) { super(); /** @private */ - this._id = getInstanceNumber(); + this._id = getNextId(); /** @private */ this._eventEmitter = nativeEventEmitter; - /** @private @type {TcpSocket[]} */ - this._connections = []; + /** @private @type {Set} */ + this._connections = new Set(); /** @private */ this._localAddress = undefined; /** @private */ @@ -30,6 +30,7 @@ export default class Server extends EventEmitter { this.listening = false; this._registerEvents(); if (connectionCallback) this.on('connection', connectionCallback); + this.on('close', this._setDisconnected, this); } /** @@ -67,7 +68,7 @@ export default class Server extends EventEmitter { * @returns {Server} */ getConnections(callback) { - callback(null, this._connections.length); + callback(null, this._connections.size); return this; } @@ -86,6 +87,7 @@ export default class Server extends EventEmitter { return this; } if (callback) this.once('close', callback); + this.listening = false; Sockets.close(this._id); return this; } @@ -95,7 +97,7 @@ export default class Server extends EventEmitter { * on an IP socket (useful to find which port was assigned when getting an OS-assigned address): * `{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`. * - * @returns {import('./TcpSocket').AddressInfo | null} + * @returns {import('./Socket').AddressInfo | null} */ address() { if (!this._localAddress) return null; @@ -128,44 +130,37 @@ export default class Server extends EventEmitter { this.close(); this.emit('error', evt.error); }); - this._closeListener = this._eventEmitter.addListener('close', (evt) => { - if (evt.id !== this._id) return; - this._setDisconnected(); - this.emit('close'); - }); this._connectionsListener = this._eventEmitter.addListener('connection', (evt) => { if (evt.id !== this._id) return; const newSocket = this._buildSocket(evt.info); - this._connections.push(newSocket); + // Emit 'close' when all connection closed + newSocket.on('close', () => { + this._connections.delete(newSocket); + if (!this.listening && this._connections.size === 0) this.emit('close'); + }); + this._connections.add(newSocket); this.emit('connection', newSocket); }); } - /** - * @private - */ - _unregisterEvents() { - this._errorListener?.remove(); - this._closeListener?.remove(); - this._connectionsListener?.remove(); - } - /** * @private */ _setDisconnected() { - this._unregisterEvents(); this._localAddress = undefined; this._localPort = undefined; this._localFamily = undefined; - this.listening = false; } /** * @private - * @param {{ id: number; connection: import('./TcpSocket').NativeConnectionInfo; }} info + * @param {{ id: number; connection: import('./Socket').NativeConnectionInfo; }} info + * @returns {Socket} */ _buildSocket(info) { - return new TcpSocket(info.id, this._eventEmitter, info.connection); + const newSocket = new Socket(); + newSocket._setId(info.id); + newSocket._setConnected(info.connection); + return newSocket; } } diff --git a/src/TcpSocket.js b/src/Socket.js similarity index 96% rename from src/TcpSocket.js rename to src/Socket.js index 3455d42..ab89e4f 100644 --- a/src/TcpSocket.js +++ b/src/Socket.js @@ -4,6 +4,7 @@ import { NativeModules, Image } from 'react-native'; import EventEmitter from 'eventemitter3'; import { Buffer } from 'buffer'; const Sockets = NativeModules.TcpSockets; +import { nativeEventEmitter, getNextId } from './Globals'; const STATE = { DISCONNECTED: 0, @@ -35,20 +36,16 @@ const STATE = { * * @extends {EventEmitter<'connect' | 'timeout' | 'data' | 'error' | 'close', any>} */ -export default class TcpSocket extends EventEmitter { +export default class Socket extends EventEmitter { /** - * Initialices a TcpSocket. - * - * @param {number} id - * @param {NativeEventEmitter} eventEmitter - * @param {NativeConnectionInfo} [connectionInfo] + * Creates a new socket object. */ - constructor(id, eventEmitter, connectionInfo) { + constructor() { super(); /** @private */ - this._id = id; + this._id = undefined; /** @private */ - this._eventEmitter = eventEmitter; + this._eventEmitter = nativeEventEmitter; /** @type {number} @private */ this._timeoutMsecs = 0; /** @private */ @@ -63,7 +60,28 @@ export default class TcpSocket extends EventEmitter { this.remotePort = undefined; this.remoteFamily = undefined; this._registerEvents(); - if (connectionInfo != undefined) this._setConnected(connectionInfo); + } + + /** + * @package + * @param {number} id + */ + _setId(id) { + this._id = id; + this._registerEvents(); + } + + /** + * @package + * @param {NativeConnectionInfo} connectionInfo + */ + _setConnected(connectionInfo) { + this._state = STATE.CONNECTED; + this.localAddress = connectionInfo.localAddress; + this.localPort = connectionInfo.localPort; + this.remoteAddress = connectionInfo.remoteAddress; + this.remoteFamily = connectionInfo.remoteFamily; + this.remotePort = connectionInfo.remotePort; } /** @@ -71,6 +89,8 @@ export default class TcpSocket extends EventEmitter { * @param {() => void} [callback] */ connect(options, callback) { + if (this._id === undefined) this._setId(getNextId()); + const customOptions = { ...options }; // Normalize args customOptions.host = customOptions.host || 'localhost'; @@ -327,19 +347,6 @@ export default class TcpSocket extends EventEmitter { } } - /** - * @private - * @param {NativeConnectionInfo} connectionInfo - */ - _setConnected(connectionInfo) { - this._state = STATE.CONNECTED; - this.localAddress = connectionInfo.localAddress; - this.localPort = connectionInfo.localPort; - this.remoteAddress = connectionInfo.remoteAddress; - this.remoteFamily = connectionInfo.remoteFamily; - this.remotePort = connectionInfo.remotePort; - } - /** * @private */ diff --git a/src/index.js b/src/index.js index f823981..eae7b16 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,6 @@ 'use strict'; -import { nativeEventEmitter, getInstanceNumber } from './Globals'; -import Socket from './TcpSocket'; +import Socket from './Socket'; import Server from './Server'; /** @@ -13,13 +12,16 @@ function createServer(connectionListener) { } /** - * @param {import('./TcpSocket').ConnectionOptions} options + * @param {import('./Socket').ConnectionOptions} options * @param {() => void} callback * @returns {Socket} */ function createConnection(options, callback) { - const tcpSocket = new Socket(getInstanceNumber(), nativeEventEmitter); + const tcpSocket = new Socket(); return tcpSocket.connect(options, callback); } -export default { createServer, createConnection, Server }; +export default { createServer, createConnection, Server, Socket }; + +// @ts-ignore +module.exports = { createServer, createConnection, Server, Socket }; diff --git a/tsconfig.json b/tsconfig.json index 17258ab..03ddc21 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowJs": true, + "lib": ["es2015"], "checkJs": true, "noEmit": true, "strict": true,