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/global.json b/global.json
index e4dc40d..a171032 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.102",
+ "version": "10.0.100-rc",
"rollForward": "latestMinor"
}
}
\ No newline at end of file
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/PingPong.csproj b/samples/PingPong/PingPong.csproj
index dc6f601..7dfdac4 100644
--- a/samples/PingPong/PingPong.csproj
+++ b/samples/PingPong/PingPong.csproj
@@ -1,7 +1,7 @@
Exe
- net8.0
+ net10.0
PingPong
disable
enable
@@ -13,7 +13,8 @@
-
+
+
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/EngineIO.Client.csproj b/src/EngineIO.Client/EngineIO.Client.csproj
index 1eae9df..246d75c 100644
--- a/src/EngineIO.Client/EngineIO.Client.csproj
+++ b/src/EngineIO.Client/EngineIO.Client.csproj
@@ -6,8 +6,9 @@
-
-
+
+
+
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..3a9586f
--- /dev/null
+++ b/src/SocketIO.Client/SocketIO.Client.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.1
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/EngineIO.Client.Tests/EngineIO.Client.Tests.csproj b/tests/EngineIO.Client.Tests/EngineIO.Client.Tests.csproj
index 397b3a8..b58e3e8 100644
--- a/tests/EngineIO.Client.Tests/EngineIO.Client.Tests.csproj
+++ b/tests/EngineIO.Client.Tests/EngineIO.Client.Tests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
EngineIO.Client.Tests
@@ -10,17 +10,18 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
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..e1fbfac
--- /dev/null
+++ b/tests/SocketIO.Client.Tests/SocketIO.Client.Tests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net10.0
+ enable
+ enable
+ SocketIO.Client.Tests
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+