diff --git a/SocketIO.sln b/SocketIO.sln index aec678e..0397db5 100644 --- a/SocketIO.sln +++ b/SocketIO.sln @@ -15,6 +15,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D8B5DE1E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineIO.Client.Tests", "tests\EngineIO.Client.Tests\EngineIO.Client.Tests.csproj", "{5D824F05-5793-4768-99DF-E4D993F16B75}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketIO.Client", "src\SocketIO.Client\SocketIO.Client.csproj", "{222889F5-6A36-4A8A-8C78-A910E40D4F82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketIO.Client.Tests", "tests\SocketIO.Client.Tests\SocketIO.Client.Tests.csproj", "{E0C636E7-91B0-44AE-9752-F08326C7C5F2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,10 +40,20 @@ Global {5D824F05-5793-4768-99DF-E4D993F16B75}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D824F05-5793-4768-99DF-E4D993F16B75}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D824F05-5793-4768-99DF-E4D993F16B75}.Release|Any CPU.Build.0 = Release|Any CPU + {222889F5-6A36-4A8A-8C78-A910E40D4F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {222889F5-6A36-4A8A-8C78-A910E40D4F82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {222889F5-6A36-4A8A-8C78-A910E40D4F82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {222889F5-6A36-4A8A-8C78-A910E40D4F82}.Release|Any CPU.Build.0 = Release|Any CPU + {E0C636E7-91B0-44AE-9752-F08326C7C5F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0C636E7-91B0-44AE-9752-F08326C7C5F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0C636E7-91B0-44AE-9752-F08326C7C5F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0C636E7-91B0-44AE-9752-F08326C7C5F2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {D1C745FE-52AF-4145-8243-9FCA91BE7916} = {80C612B0-98E1-4E6B-AF10-5BB3087F0864} {4F6177C5-3234-4897-B61C-49C44D3E2EF8} = {577735D4-BC7E-464A-BC9B-93AD8B5B6738} {5D824F05-5793-4768-99DF-E4D993F16B75} = {D8B5DE1E-C83F-40A9-9D27-D2FBB3866F0A} + {222889F5-6A36-4A8A-8C78-A910E40D4F82} = {577735D4-BC7E-464A-BC9B-93AD8B5B6738} + {E0C636E7-91B0-44AE-9752-F08326C7C5F2} = {D8B5DE1E-C83F-40A9-9D27-D2FBB3866F0A} EndGlobalSection EndGlobal diff --git a/package-lock.json b/package-lock.json index 5bed53d..87fe015 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,40 +9,44 @@ "version": "1.0.0", "license": "ISC", "workspaces": [ - "samples/simple-engine-io-server" + "samples/simple-engine-io-server", + "samples/simple-socket-io-server" ] }, - "node_modules/simple-engine-io-server": { - "resolved": "samples/simple-engine-io-server", - "link": true - }, - "samples/simple-engine-io-server": { - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "engine.io": "^6.5.4" - } + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, - "samples/simple-engine-io-server/node_modules/@types/cookie": { + "node_modules/@types/cookie": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", "license": "MIT" }, - "samples/simple-engine-io-server/node_modules/@types/cors": { + "node_modules/@types/cors": { "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "license": "MIT", "dependencies": { "@types/node": "*" } }, - "samples/simple-engine-io-server/node_modules/@types/node": { - "version": "20.11.20", + "node_modules/@types/node": { + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, - "samples/simple-engine-io-server/node_modules/accepts": { + "node_modules/accepts": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -52,22 +56,28 @@ "node": ">= 0.6" } }, - "samples/simple-engine-io-server/node_modules/base64id": { + "node_modules/base64id": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "license": "MIT", "engines": { "node": "^4.5.0 || >= 5.9" } }, - "samples/simple-engine-io-server/node_modules/cookie": { + "node_modules/cookie": { "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "samples/simple-engine-io-server/node_modules/cors": { + "node_modules/cors": { "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -77,11 +87,13 @@ "node": ">= 0.10" } }, - "samples/simple-engine-io-server/node_modules/debug": { - "version": "4.3.4", + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -92,8 +104,10 @@ } } }, - "samples/simple-engine-io-server/node_modules/engine.io": { - "version": "6.5.4", + "node_modules/engine.io": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.1.tgz", + "integrity": "sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==", "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", @@ -105,28 +119,34 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" } }, - "samples/simple-engine-io-server/node_modules/engine.io-parser": { - "version": "5.2.2", + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "license": "MIT", "engines": { "node": ">=10.0.0" } }, - "samples/simple-engine-io-server/node_modules/mime-db": { + "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "samples/simple-engine-io-server/node_modules/mime-types": { + "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -135,44 +155,105 @@ "node": ">= 0.6" } }, - "samples/simple-engine-io-server/node_modules/ms": { - "version": "2.1.2", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "samples/simple-engine-io-server/node_modules/negotiator": { + "node_modules/negotiator": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "samples/simple-engine-io-server/node_modules/object-assign": { + "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "samples/simple-engine-io-server/node_modules/undici-types": { - "version": "5.26.5", + "node_modules/simple-engine-io-server": { + "resolved": "samples/simple-engine-io-server", + "link": true + }, + "node_modules/simple-socket-io-server": { + "resolved": "samples/simple-socket-io-server", + "link": true + }, + "node_modules/socket.io": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, - "samples/simple-engine-io-server/node_modules/vary": { + "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "samples/simple-engine-io-server/node_modules/ws": { - "version": "8.11.0", + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -182,6 +263,20 @@ "optional": true } } + }, + "samples/simple-engine-io-server": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "engine.io": "^6.5.4" + } + }, + "samples/simple-socket-io-server": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "socket.io": "^4.8.0" + } } } } diff --git a/package.json b/package.json index 4ec4c88..2319000 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,13 @@ "description": "Socket.IO .NET client samples test server and tools", "directories": {}, "scripts": { - "start:server": "npm start -w samples/simple-engine-io-server" + "start:engine": "npm start -w samples/simple-engine-io-server", + "start:socket": "npm start -w samples/simple-socket-io-server" }, "author": "Redhouane Sobaihi", "license": "ISC", "workspaces": [ - "samples/simple-engine-io-server" + "samples/simple-engine-io-server", + "samples/simple-socket-io-server" ] } diff --git a/samples/PingPong/Program.cs b/samples/PingPong/Program.cs index ccbe6dc..b8688ed 100644 --- a/samples/PingPong/Program.cs +++ b/samples/PingPong/Program.cs @@ -6,17 +6,23 @@ using EngineIO.Client; using Microsoft.Extensions.Logging; +#if DEBUG using Microsoft.Extensions.Logging.Console; +#else +using Microsoft.Extensions.Logging.Abstractions; +#endif namespace PingPong; internal class Program : IDisposable { + private readonly ILoggerFactory loggerFactory; private readonly ILogger logger; private static readonly CancellationTokenSource cts = new(); Program() { +#if DEBUG var loggerFactory = LoggerFactory.Create(builder => { builder.AddSimpleConsole(o => @@ -26,8 +32,12 @@ internal class Program : IDisposable o.ColorBehavior = LoggerColorBehavior.Enabled; }).SetMinimumLevel(LogLevel.Debug); }); - + logger = loggerFactory.CreateLogger(); +#else + this.loggerFactory = NullLoggerFactory.Instance; + this.logger = loggerFactory.CreateLogger(); +#endif Engine = new Engine((options) => { options.BaseAddress = "http://127.0.0.1:9854"; @@ -48,7 +58,7 @@ private static async Task Main(string[] args) program.logger.LogDebug("Client Starting..."); await program.Engine.ConnectAsync(cts.Token); - await Task.WhenAll(Task.Run(program.EmitAsync), Task.Run(program.ListenAsync)); + await Task.WhenAll(Task.Run(program.EmitAsync, cts.Token), Task.Run(program.ListenAsync, cts.Token)); program.logger.LogDebug("Disconnecting..."); await program.Engine.DisconnectAsync(); diff --git a/samples/simple-engine-io-server/package-lock.json b/samples/simple-engine-io-server/package-lock.json deleted file mode 100644 index 40f75d1..0000000 --- a/samples/simple-engine-io-server/package-lock.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "name": "simple-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "simple-server", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "engine.io": "^6.5.4" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.11.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", - "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/samples/simple-socket-io-server/index.js b/samples/simple-socket-io-server/index.js new file mode 100644 index 0000000..bc72d46 --- /dev/null +++ b/samples/simple-socket-io-server/index.js @@ -0,0 +1,19 @@ +const { Server } = require('socket.io'); + +const io = new Server({ + cors: { + origin: '*', + methods: ['GET', 'POST'] + }, +}) + + +io.on("connection", (socket) => { + // ... + socket.on('message', (data, callback) => { + console.log(data); + callback?.apply(this,['OK']); + }) +}); + +io.listen(process.env.PORT || 3000); diff --git a/samples/simple-socket-io-server/package.json b/samples/simple-socket-io-server/package.json new file mode 100644 index 0000000..6aa6a01 --- /dev/null +++ b/samples/simple-socket-io-server/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-socket-io-server", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "DEBUG=engine,socket.io* node index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "socket.io": "^4.8.0" + } +} diff --git a/src/EngineIO.Client/ClientOptions.cs b/src/EngineIO.Client/ClientOptions.cs index 789c965..88db4bb 100644 --- a/src/EngineIO.Client/ClientOptions.cs +++ b/src/EngineIO.Client/ClientOptions.cs @@ -5,7 +5,12 @@ public class ClientOptions /// /// Engine.io server Uri. /// - public string BaseAddress { get; set; } = null!; + public string? BaseAddress { get; set; } + + /// + /// Engine.io path, default to "engine.io" + /// + public string? Path { get; set; } /// /// Flag indicating whether client should automatically update from HTTP polling to websocket transport. diff --git a/src/EngineIO.Client/Engine.cs b/src/EngineIO.Client/Engine.cs index 3e1ad12..56e26b6 100644 --- a/src/EngineIO.Client/Engine.cs +++ b/src/EngineIO.Client/Engine.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -109,7 +110,7 @@ private async Task PollAsync() { #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - _transport.SendAsync(Packet.PongPacket.ToPlaintextPacket(), PacketFormat.PlainText, + _transport.SendAsync(Packet.PongPacket.ToWirePacket(), PacketFormat.PlainText, _pollingCancellationTokenSource.Token); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed @@ -172,7 +173,8 @@ public async IAsyncEnumerable ListenAsync([EnumeratorCancellation] Cance /// public async Task SendAsync(string text, CancellationToken cancellationToken = default) { - var packet = Packet.CreateMessagePacket(text).ToPlaintextPacket(); + var body = Encoding.UTF8.GetBytes(text); + var packet = Packet.CreatePacket(PacketFormat.PlainText, body).ToWirePacket(); await _transport.SendAsync(packet, PacketFormat.PlainText, cancellationToken); } @@ -183,7 +185,7 @@ public async Task SendAsync(string text, CancellationToken cancellationToken = d /// public async Task SendAsync(ReadOnlyMemory binary, CancellationToken cancellationToken = default) { - var packet = Packet.CreateBinaryPacket(binary).ToBinaryPacket(_base64Encoder); + var packet = Packet.CreatePacket(PacketFormat.Binary, binary).ToWirePacket(_base64Encoder); await _transport.SendAsync(packet, PacketFormat.Binary, cancellationToken); } } \ No newline at end of file diff --git a/src/EngineIO.Client/Packets/Packet.cs b/src/EngineIO.Client/Packets/Packet.cs index 912f34a..9055869 100644 --- a/src/EngineIO.Client/Packets/Packet.cs +++ b/src/EngineIO.Client/Packets/Packet.cs @@ -1,5 +1,4 @@ using System; -using System.Text; namespace EngineIO.Client.Packets; @@ -42,15 +41,11 @@ public static bool TryParse(ReadOnlyMemory data, out Packet packet) return true; } - public static Packet CreateMessagePacket(string text) + public static Packet CreatePacket(PacketFormat format, ReadOnlyMemory body) { - var body = Encoding.UTF8.GetBytes(text); - return new Packet(PacketFormat.PlainText, PacketType.Message, body); - } - - public static Packet CreateBinaryPacket(ReadOnlyMemory body) - { - return new Packet(PacketFormat.Binary, PacketType.Message, body); + return format == PacketFormat.Binary ? + new Packet(PacketFormat.Binary, PacketType.Message, body) + : new Packet(PacketFormat.PlainText, PacketType.Message, body); } public Packet(PacketFormat format, PacketType type, ReadOnlyMemory body) @@ -58,6 +53,7 @@ public Packet(PacketFormat format, PacketType type, ReadOnlyMemory body) Format = format; Type = format == PacketFormat.Binary ? PacketType.Message : type; Body = body; + // count body content bytes length and packet type byte Length = body.Length + 1; } diff --git a/src/EngineIO.Client/Transports/HttpPollingTransport.cs b/src/EngineIO.Client/Transports/HttpPollingTransport.cs index 87b2c44..b498807 100644 --- a/src/EngineIO.Client/Transports/HttpPollingTransport.cs +++ b/src/EngineIO.Client/Transports/HttpPollingTransport.cs @@ -59,7 +59,7 @@ public async Task Disconnect() { if (_connected) { - await SendAsync(Packet.ClosePacket.ToPlaintextPacket(), PacketFormat.PlainText); + await SendAsync(Packet.ClosePacket.ToWirePacket(), PacketFormat.PlainText); } _connected = false; diff --git a/src/EngineIO.Client/Transports/PacketExtensions.cs b/src/EngineIO.Client/Transports/PacketExtensions.cs index 940dcdf..9b968c1 100644 --- a/src/EngineIO.Client/Transports/PacketExtensions.cs +++ b/src/EngineIO.Client/Transports/PacketExtensions.cs @@ -13,13 +13,15 @@ public static class PacketExtensions /// Packet instance /// /// - public static ReadOnlyMemory ToPlaintextPacket(this Packet packet) + public static ReadOnlyMemory ToWirePacket(this Packet packet) { if (packet.Format != PacketFormat.PlainText && packet.Type != PacketType.Message) { throw new InvalidOperationException("Wrong packet format"); } + // TODO: revisit this implementation for potential improvements + var payload = new byte[1 + packet.Body.Length]; payload[0] = (byte)packet.Type; for (var i = 1; i <= packet.Body.Length; i++) @@ -37,13 +39,15 @@ public static ReadOnlyMemory ToPlaintextPacket(this Packet packet) /// Binary packet encoder /// /// - public static ReadOnlyMemory ToBinaryPacket(this Packet packet, IEncoder encoder) + public static ReadOnlyMemory ToWirePacket(this Packet packet, IEncoder encoder) { if (packet.Format != PacketFormat.Binary && packet.Type != PacketType.Message) { throw new InvalidOperationException("Wrong packet format"); } + // TODO: revisit this implementation for potential improvement + var encodedBody = encoder.Encode(packet.Body, Encoding.UTF8); var payload = new byte[1 + encodedBody.Length]; payload[0] = (byte)'b'; diff --git a/src/EngineIO.Client/Transports/WebSocketTransport.cs b/src/EngineIO.Client/Transports/WebSocketTransport.cs index b15a87f..32d89ff 100644 --- a/src/EngineIO.Client/Transports/WebSocketTransport.cs +++ b/src/EngineIO.Client/Transports/WebSocketTransport.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.ObjectModel; using System.IO; using System.Net.WebSockets; @@ -70,7 +71,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default) await _client.ConnectAsync(_uri, cancellationToken); // ping probe - await SendAsync(Packet.PingProbePacket.ToPlaintextPacket(), PacketFormat.PlainText, cancellationToken); + await SendAsync(Packet.PingProbePacket.ToWirePacket(), PacketFormat.PlainText, cancellationToken); // pong probe var data = await GetAsync(cancellationToken); @@ -85,7 +86,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default) } // upgrade - await SendAsync(Packet.UpgradePacket.ToPlaintextPacket(), PacketFormat.PlainText, cancellationToken); + await SendAsync(Packet.UpgradePacket.ToWirePacket(), PacketFormat.PlainText, cancellationToken); _connected = true; } @@ -104,13 +105,14 @@ public Task Disconnect() public async Task>> GetAsync(CancellationToken cancellationToken = default) { var packets = new Collection>(); - var stream = new MemoryStream(); - var buffer = new byte[16]; - + using var stream = new MemoryStream(); + using var rent = MemoryPool.Shared.Rent(16); + Memory buffer = rent.Memory; + try { await _receiveSemaphore.WaitAsync(CancellationToken.None); - WebSocketReceiveResult result; + ValueWebSocketReceiveResult result; do { result = await _client.ReceiveAsync(buffer, cancellationToken); @@ -121,7 +123,7 @@ public async Task>> GetAsync(Cancellatio break; } - await stream.WriteAsync(buffer, 0, result.Count, cancellationToken); + await stream.WriteAsync(buffer, cancellationToken); } while (!result.EndOfMessage); } finally diff --git a/src/SocketIO.Client/AssemblyInfo.cs b/src/SocketIO.Client/AssemblyInfo.cs new file mode 100644 index 0000000..7cf8eef --- /dev/null +++ b/src/SocketIO.Client/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SocketIO.Client.Tests")] \ No newline at end of file diff --git a/src/SocketIO.Client/IO.cs b/src/SocketIO.Client/IO.cs new file mode 100644 index 0000000..0893fd4 --- /dev/null +++ b/src/SocketIO.Client/IO.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using EngineIO.Client; +using EngineIO.Client.Transports; +using PacketFormat = EngineIO.Client.Packets.PacketFormat; + +using SocketIO.Client.Packets; + + +namespace SocketIO.Client; + +public class IO +{ + private static readonly string DefaultPath = "socket.io"; + + private readonly Engine _client; + private readonly string _baseUrl; + + public string Path { get; set; } = DefaultPath; + + // Map namespace with its corresponding sid + private readonly Dictionary _namespaces = new(); + + public IO(string baseUrl) + { + this._baseUrl = baseUrl; + + this._client = new Engine((config) => + { + config.BaseAddress = baseUrl; + config.AutoUpgrade = true; + config.Path = Path; + // TODO: allow passing custom headers and queries + }); + } + + public Task Connect(string? @namespace = default) + { + return Task.CompletedTask; + } + + public async IAsyncEnumerable> ListenAsync( + string? @namespace = default, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + // TODO: + yield return Array.Empty(); + } + + private Task _Send(Packet packet) + { + var buffer = packet.Serialize().ToArray(); + var eioPacker = EngineIO.Client.Packets.Packet.CreatePacket(PacketFormat.PlainText, buffer); + + return this._client.SendAsync(eioPacker.ToWirePacket()); + } + + public Task SendAsync(ReadOnlyMemory data, string? @namespace = null) + { + var packet = new Packet(PacketType.BinaryEvent, @namespace); + packet.AddItem(data); + + // TODO: + return this._Send(packet); + } + + public Task SendAsync(string text, string? @namespace = null) + { + var packet = new Packet(PacketType.Event, @namespace); + packet.AddItem(text); + return this._Send(packet); + } + + public Task SendAsync(T data, string? @namespace = null) where T : class + { + var packet = new Packet(PacketType.Event, @namespace); + packet.AddItem(data); + return this._Send(packet); + } +} \ No newline at end of file diff --git a/src/SocketIO.Client/Packets/PacketData.cs b/src/SocketIO.Client/Packets/PacketData.cs new file mode 100644 index 0000000..babc55a --- /dev/null +++ b/src/SocketIO.Client/Packets/PacketData.cs @@ -0,0 +1,63 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SocketIO.Client.Packets; + +internal interface IPacketData +{ + void Serialize(Utf8JsonWriter stream); +} + +internal sealed class TextPacketData : IPacketData +{ + public string Data { get; } + + public TextPacketData(string data) + { + Data = data; + } + + public void Serialize(Utf8JsonWriter stream) + { + stream.WriteStringValue(this.Data); + } +} + +internal sealed class JsonPacketData : IPacketData where T : class +{ + public T Data { get; } + + public JsonPacketData(T data) + { + Data = data; + } + + public void Serialize(Utf8JsonWriter stream) + { + JsonSerializer.Serialize(stream, Data); + } +} + +internal sealed class BinaryPacketData : IPacketData +{ + public BinaryPacketData(int id, ReadOnlyMemory data) + { + Id = id; + Data = data; + } + + [JsonIgnore] + public ReadOnlyMemory Data { get; } + + [JsonPropertyName("_placeholder")] + public bool Placeholder => true; + + [JsonPropertyName("num")] + public int Id { get; } + + public void Serialize(Utf8JsonWriter stream) + { + JsonSerializer.Serialize(stream, this); + } +} diff --git a/src/SocketIO.Client/Packets/PacketType.cs b/src/SocketIO.Client/Packets/PacketType.cs new file mode 100644 index 0000000..28d5867 --- /dev/null +++ b/src/SocketIO.Client/Packets/PacketType.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +namespace SocketIO.Client.Packets; + +public enum PacketType : byte +{ + /// + /// Connect packet type + /// + Connect = 0x30, + + /// + /// Disconnect packet type + /// + Disconnect = 0x31, + + /// + /// Event packet type with Plaintext/JSON data + /// + Event = 0x32, + + /// + /// Acknowledgement packet type with plaintext/json data + /// + Ack = 0x33, + + /// + /// Connection error packet type + /// + ConnectError = 0x34, + + /// + /// Event packet type with binary data + /// + BinaryEvent = 0x35, + + /// + /// Acknowledgement packet type with binary data + /// + BinaryAck = 0x36 +} + +/** + * SocketIO packet format: + * + * Initial Packet: + * [<# of binary attachments>-][,][][JSON-stringified payload without binary] + * + * Next Packets containing binary data + * + binary attachments extracted + * + */ + +public class Packet +{ + public static readonly Packet ConnectPacket = new(PacketType.Connect); + + public static Packet DisconnectPacket = new(PacketType.Disconnect); + + const string DefaultNamespace = "/"; + + const string DefaultEventName = "message"; + + public PacketType Type { get; } + public string? Namespace { get; } + public int? AckId { get; } + public string? Event { get; } + + private readonly List _data = new(); + + private readonly MemoryStream _buffer = new(); + + public Packet(PacketType type) + : this(type, DefaultNamespace, DefaultEventName) + { + Type = type; + } + + public Packet(PacketType type, string? @namespace) + : this(type, @namespace, DefaultEventName) + { + Type = type; + Namespace = @namespace; + } + + public Packet(PacketType type, string? @namespace, string? @event) + { + if ((type is PacketType.Connect or PacketType.Disconnect) && @event != DefaultEventName) + { + throw new ArgumentException(); + } + + Type = type; + Namespace = @namespace ?? DefaultNamespace; + Event = @event ?? DefaultEventName; + + if (type is PacketType.Event or PacketType.Ack or PacketType.BinaryEvent or PacketType.BinaryAck) + { + this._AddItem(new TextPacketData(Event)); + } + } + + public Packet(PacketType type, int ackId, string? @namespace, string? @event) + : this(type, @namespace, @event) + { + if (type != PacketType.Ack && type != PacketType.BinaryAck) + { + throw new ArgumentException(); + } + + AckId = ackId; + } + + private void _AddItem(T data) where T : IPacketData + { + if (Type == PacketType.Connect || Type == PacketType.Disconnect) + { + throw new InvalidOperationException(); + } + + this._data.Add(data); + } + + /// + /// Add plain text data to packet + /// + /// Plain text data + public void AddItem(string data) + { + if (Type == PacketType.BinaryEvent || Type == PacketType.BinaryAck) + { + throw new InvalidOperationException(); + } + + this._AddItem(new TextPacketData(data)); + } + + /// + /// Add binary data + /// + /// Binary data + public void AddItem(ReadOnlyMemory data) + { + if (Type != PacketType.BinaryEvent && Type != PacketType.BinaryAck) + { + throw new InvalidOperationException(); + } + + int id = this._data.OfType().Count(); + this._AddItem(new BinaryPacketData(id, data)); + } + + /// + /// Add Json serializable POCO + /// + /// Data instance + /// Data type + public void AddItem(T data) where T : class + { + if (Type == PacketType.BinaryEvent || Type == PacketType.BinaryAck) + { + throw new InvalidOperationException(); + } + + this._AddItem(new JsonPacketData(data)); + } + + /// + /// Serialize the packet and return the underlying memory stream which contains the raw packet bytes. + /// + /// The underlying `MemoryStream` + internal MemoryStream Serialize() + { + // write packet header + using var headerWriter = new StreamWriter(this._buffer); + using var dataWriter = new Utf8JsonWriter(this._buffer); + + // write packet type + headerWriter.Write((char)this.Type); + if (Type is PacketType.BinaryEvent || Type == PacketType.BinaryAck) + { + var count = this._data.OfType().Count(); + if (count > 0) + { + headerWriter.Write(count); + headerWriter.Write('-'); + } + } + + // write packet namespace + if (Namespace != DefaultNamespace) + { + headerWriter.Write('/'); + headerWriter.Write(Namespace); + headerWriter.Write(','); + } + + // write acknowledgement id if set + if (AckId.HasValue) + { + headerWriter.Write(AckId); + } + headerWriter.Flush(); + + // write packet content + + // Connect and Disconnect packet do not contain payload + // therefore, writing packet payload is skipped + if (Type == PacketType.Connect || Type == PacketType.Disconnect) + { + return this._buffer; + } + + dataWriter.WriteStartArray(); + foreach (IPacketData item in this._data) + { + item.Serialize(dataWriter); + } + dataWriter.WriteEndArray(); + dataWriter.Flush(); + + return this._buffer; + } +} \ No newline at end of file diff --git a/src/SocketIO.Client/SocketIO.Client.csproj b/src/SocketIO.Client/SocketIO.Client.csproj new file mode 100644 index 0000000..d8fe417 --- /dev/null +++ b/src/SocketIO.Client/SocketIO.Client.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + enable + + + + + + + diff --git a/tests/EngineIO.Client.Tests/Packets/PacketTests.cs b/tests/EngineIO.Client.Tests/Packets/PacketTests.cs index 09332d9..6a85c03 100644 --- a/tests/EngineIO.Client.Tests/Packets/PacketTests.cs +++ b/tests/EngineIO.Client.Tests/Packets/PacketTests.cs @@ -1,5 +1,3 @@ -using System.Text; - using EngineIO.Client.Packets; using EngineIO.Client.Transports; @@ -14,7 +12,7 @@ void Create_Payload_From_Plaintext_Message() PacketFormat.PlainText, PacketType.Message, new[] { (byte)'H', (byte)'i' }) - .ToPlaintextPacket(); + .ToWirePacket(); Assert.Equal((byte)PacketType.Message, packet.Span[0]); Assert.Equal((byte)'H', packet.Span[1]); @@ -24,13 +22,14 @@ void Create_Payload_From_Plaintext_Message() [Fact] void Create_Payload_From_Binary_Message() { - var body = Encoding.UTF8.GetBytes("Hi"); + var body = "Hi"u8.ToArray(); var base64 = Convert.ToBase64String(body); + var packet = new Packet( PacketFormat.Binary, PacketType.Message, new[] { (byte)'H', (byte)'i' }) - .ToBinaryPacket(new Base64Encoder()); + .ToWirePacket(new Base64Encoder()); Assert.Equal((byte)'b', packet.Span[0]); Assert.Equal((byte)base64[0], packet.Span[1]); @@ -65,6 +64,6 @@ void Parse_Should_Throw_Invalid_Packet_Type() void PingProbePacket_Should_Be_Valid() { var packet = Packet.PingProbePacket; - Assert.Equal((byte)PacketType.Ping, packet.ToPlaintextPacket().Span[0]); + Assert.Equal((byte)PacketType.Ping, packet.ToWirePacket().Span[0]); } } \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/GlobalUsings.cs b/tests/SocketIO.Client.Tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/SocketIO.Client.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/Packets/BinaryEventPacketTests.cs b/tests/SocketIO.Client.Tests/Packets/BinaryEventPacketTests.cs new file mode 100644 index 0000000..f73f29c --- /dev/null +++ b/tests/SocketIO.Client.Tests/Packets/BinaryEventPacketTests.cs @@ -0,0 +1,109 @@ +using System.Text; + +using SocketIO.Client.Packets; + +namespace SocketIO.Client.Tests.Packets; + +public class BinaryEventPacketTests +{ + [Fact] + void ShouldCreateBinaryEventPacket() + { + var packetType = PacketType.BinaryEvent; + var expectedPacketType = packetType; + var expectedDefaultNamespace = "/"; + + var packet = new Packet(packetType); + packet.AddItem(new ReadOnlyMemory([ 1, 2, 3 ])); + + Assert.Equal(expectedPacketType, packet.Type); + Assert.Equal(expectedDefaultNamespace, packet.Namespace); + } + + [Fact] + public void ShouldCreateBinaryPacketWithNamespace() + { + var @namespace = "test"; + var expectedNamespace = @namespace; + + var packet = new Packet(PacketType.BinaryEvent, @namespace); + + Assert.Equal(expectedNamespace, packet.Namespace); + } + + [Fact] + public void ShouldCreateBinaryPacketWithEventName() + { + var eventName = "test"; + var expectedEventName = eventName; + + var packet = new Packet(PacketType.BinaryEvent, null, eventName); + + Assert.Equal(expectedEventName, packet.Event); + } + + [Fact] + public void ShouldCreateBinaryPacketWithAckId() + { + var ackId = 42; + var expectedAckId = ackId; + + var packet = new Packet(PacketType.BinaryAck, ackId, null, null); + + Assert.Equal(expectedAckId, packet.AckId); + } + + [Fact] + public void ShouldSerializeBinaryEventPacket() + { + var expectedEncodedPacket = $$"""51-["message",{"_placeholder":true,"num":0}]"""; + var packet = new Packet(PacketType.BinaryEvent); + packet.AddItem(new ReadOnlyMemory([ 1, 2, 3 ])); + + var encodedPacket = Encoding.UTF8.GetString( packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + public void ShouldSerializeBinaryPacketWithNamespace() + { + var @namespace = "test"; + var expectedEncodedPacket = $$"""51-/{{@namespace}},["message",{"_placeholder":true,"num":0}]"""; + + var packet = new Packet(PacketType.BinaryEvent, @namespace); + packet.AddItem(new ReadOnlyMemory([ 1, 2, 3 ])); + + var encodedPacket = Encoding.UTF8.GetString( packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + public void ShouldSerializeBinaryPacketWithEventName() + { + var eventName = "test"; + var expectedEncodedPacket = $$"""51-["{{eventName}}",{"_placeholder":true,"num":0}]"""; + + var packet = new Packet(PacketType.BinaryEvent, null, eventName); + packet.AddItem(new ReadOnlyMemory([ 1, 2, 3 ])); + + var encodedPacket = Encoding.UTF8.GetString( packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + public void ShouldSerializeBinaryPacketWithAckId() + { + var ackId = 42; + var expectedEncodedPacket = $$"""61-42["message",{"_placeholder":true,"num":0}]"""; + + var packet = new Packet(PacketType.BinaryAck, ackId, null, null); + packet.AddItem(new ReadOnlyMemory([ 1, 2, 3 ])); + + var encodedPacket = Encoding.UTF8.GetString( packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } +} \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/Packets/ConnectPacketTests.cs b/tests/SocketIO.Client.Tests/Packets/ConnectPacketTests.cs new file mode 100644 index 0000000..a83fa14 --- /dev/null +++ b/tests/SocketIO.Client.Tests/Packets/ConnectPacketTests.cs @@ -0,0 +1,59 @@ +using System.Text; + +using SocketIO.Client.Packets; + +namespace SocketIO.Client.Tests.Packets; + +public class ConnectPacketTests +{ + [Fact] + void ShouldCreateConnectPacket() + { + var packet = Packet.ConnectPacket; + Assert.Equal(PacketType.Connect, packet.Type); + Assert.Equal("/", packet.Namespace); + } + + [Fact] + void ShouldCreateConnectPacketWithNamespace() + { + var @namespace = "test"; + + var packet = new Packet(PacketType.Connect, @namespace); + + Assert.Equal(PacketType.Connect, packet.Type); + Assert.Equal(@namespace, packet.Namespace); + } + + [Fact] + void ShouldSerializeConnectPacket() + { + var connectPacket = Packet.ConnectPacket; + + var serialized = connectPacket.Serialize(); + var plaintext = Encoding.UTF8.GetString(serialized.ToArray()); + + Assert.Equal(PacketType.Connect, connectPacket.Type); + Assert.Equal("0", plaintext); + } + + [Fact] + void ShouldSerializeConnectPacketWithNamespace() + { + var @namespace = "test"; + var connectPacket = new Packet(PacketType.Connect, @namespace);; + + var serialized = connectPacket.Serialize(); + var plaintext = Encoding.UTF8.GetString(serialized.ToArray()); + + Assert.Equal(PacketType.Connect, connectPacket.Type); + Assert.Equal($"0/{@namespace},", plaintext); + } + + [Fact] + void ShouldThrowExceptionWhenAddingItemToConnectPacket() + { + var packet = Packet.ConnectPacket; + Assert.Throws(() => packet.AddItem("World")); + } +} \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/Packets/DiconnectPacketTests.cs b/tests/SocketIO.Client.Tests/Packets/DiconnectPacketTests.cs new file mode 100644 index 0000000..3b2cb3f --- /dev/null +++ b/tests/SocketIO.Client.Tests/Packets/DiconnectPacketTests.cs @@ -0,0 +1,63 @@ +using System.Text; + +using SocketIO.Client.Packets; + +namespace SocketIO.Client.Tests.Packets; + +public class DisconnectPacketTests +{ + [Fact] + void ShouldCreateDisconnectPacket() + { + var packet = Packet.DisconnectPacket; + + Assert.Equal(PacketType.Disconnect, packet.Type); + Assert.Equal("/", packet.Namespace); + } + + [Fact] + void ShouldCreateDisconnectPacketWithNamespace() + { + var @namespace = "test"; + var packet = new Packet(PacketType.Disconnect, @namespace); + + Assert.Equal(PacketType.Disconnect, packet.Type); + Assert.Equal(@namespace, packet.Namespace); + } + + [Fact] + void ShouldSerializeDisconnectPacket() + { + var connectPacket = Packet.DisconnectPacket; + + var serialized = connectPacket.Serialize(); + var encodedPacket = Encoding.UTF8.GetString(serialized.ToArray()); + + string expectedEncodedPacket = "1"; + + Assert.Equal(PacketType.Disconnect, connectPacket.Type); + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializeDisconnectPacketWithNamespace() + { + var @namespace = "test"; + var packet = new Packet(PacketType.Disconnect, @namespace);; + + var serialized = packet.Serialize(); + var encodedPacket = Encoding.UTF8.GetString(serialized.ToArray()); + + string expectedEncodedPacket = $"1/{@namespace},"; + + Assert.Equal(PacketType.Disconnect, packet.Type); + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldThrowExceptionWhenAddingItemToDisonnectPacket() + { + var packet = Packet.DisconnectPacket; + Assert.Throws(() => packet.AddItem("World")); + } +} \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/Packets/EventPacketTests.cs b/tests/SocketIO.Client.Tests/Packets/EventPacketTests.cs new file mode 100644 index 0000000..d7145bc --- /dev/null +++ b/tests/SocketIO.Client.Tests/Packets/EventPacketTests.cs @@ -0,0 +1,180 @@ +using System.Text; + +using SocketIO.Client.Packets; + +namespace SocketIO.Client.Tests.Packets; + +// class used for packet with json payload tests +class Foo +{ + public string? Value { get; set; } +} + + +public class EventPacketTests +{ + [Fact] + void ShouldCreateEventPacket() + { + var packet = new Packet(PacketType.Event); + + string expectedDefaultNamespace = "/"; + string expectedDefaultEvent = "message"; + + Assert.Equal(PacketType.Event, packet.Type); + Assert.Equal(expectedDefaultNamespace, packet.Namespace); + Assert.Equal(expectedDefaultEvent, packet.Event); + } + + [Fact] + void ShouldCreateEventPacketWithNamespace() + { + var @namespace = "test"; + var packet = new Packet(PacketType.Event, "test"); + + var expectedNamespace = @namespace; + var expectedDefaultEvent = "message"; + + Assert.Equal(PacketType.Event, packet.Type); + Assert.Equal(expectedNamespace, packet.Namespace); + Assert.Equal(expectedDefaultEvent, packet.Event); + } + + [Fact] + void ShouldCreateEventPacketWithEventName() + { + var eventName = "test"; + var packet = new Packet(PacketType.Event, null, eventName); + + var expectedDefaultNamespace = "/"; + var expectedDefaultEvent = eventName; + + Assert.Equal(PacketType.Event, packet.Type); + Assert.Equal(expectedDefaultNamespace, packet.Namespace); + Assert.Equal(expectedDefaultEvent, packet.Event); + } + + [Fact] + void ShouldCreateEventPacketWithAckId() + { + var ackId = 42; + var packet = new Packet(PacketType.Ack, ackId, null, null); + + var expectedAckId = ackId; + + Assert.Equal(PacketType.Ack, packet.Type); + Assert.Equal(expectedAckId, packet.AckId); + } + + [Fact] + void ShouldSerializePlainTextEventPacket() + { + var packet = new Packet(PacketType.Event); + packet.AddItem("Hello!"); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + string expectedEncodedPacket = """2["message","Hello!"]"""; + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldThrowWhenAddingInvalidPayloadPacket() + { + var packet = new Packet(PacketType.Event); + var invalidPayload = new ReadOnlyMemory(new byte[] { 1, 2, 3 }); + Assert.Throws(() => packet.AddItem(invalidPayload)); + } + + [Fact] + void ShouldSerializePlainTextEventWithNamespace() + { + var @namespace = "test"; + var expectedEncodedPacket = $"""2/{@namespace},["message","Hello!"]"""; + + var packet = new Packet(PacketType.Event, @namespace); + packet.AddItem("Hello!"); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializePlainTextEventWithEventName() + { + var eventName = "test"; + var expectedEncodedPacket = $$"""2["{{eventName}}","Hello!"]"""; + + var packet = new Packet(PacketType.Event, null, eventName); + packet.AddItem("Hello!"); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializePlainTextWithAckIdPacket() + { + var ackId = 42; + var expectedEncodedPacket = $$"""3{{ackId}}["message","Hello!"]"""; + + var packet = new Packet(PacketType.Ack, ackId, null, null); + packet.AddItem("Hello!"); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializeJsonEventPacket() + { + var packet = new Packet(PacketType.Event); + packet.AddItem(new Foo { Value = "bar" }); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + string expectedEncodedPacket = """2["message",{"Value":"bar"}]"""; + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializeJsonEventPacketWithNamespace() + { + var @namespace = "test"; + var expectedEncodedPacket = $$"""2/{{@namespace}},["message",{"Value":"bar"}]"""; + + var packet = new Packet(PacketType.Event, @namespace); + packet.AddItem(new Foo { Value = "bar" }); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializeJsonEventWithEventName() + { + var eventName = "test"; + var expectedEncodedPacket = $$"""2["{{eventName}}",{"Value":"bar"}]"""; + + var packet = new Packet(PacketType.Event, null, eventName); + packet.AddItem(new Foo { Value = "bar" }); + + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } + + [Fact] + void ShouldSerializeJsonEventWithAckIdPacket() + { + var ackId = 42; + var expectedEncodedPacket = $$"""3{{ackId}}["message",{"Value":"bar"}]"""; + + var packet = new Packet(PacketType.Ack, ackId, null, null); + packet.AddItem(new Foo { Value = "bar" }); + var encodedPacket = Encoding.UTF8.GetString(packet.Serialize().ToArray()); + + Assert.Equal(expectedEncodedPacket, encodedPacket); + } +} \ No newline at end of file diff --git a/tests/SocketIO.Client.Tests/SocketIO.Client.Tests.csproj b/tests/SocketIO.Client.Tests/SocketIO.Client.Tests.csproj new file mode 100644 index 0000000..0aa2046 --- /dev/null +++ b/tests/SocketIO.Client.Tests/SocketIO.Client.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + SocketIO.Client.Tests + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +