├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── WebSocketManager.sln
├── samples
├── .DS_Store
├── ChatApplication
│ ├── ChatApplication.csproj
│ ├── ChatHandler.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Startup.cs
│ └── wwwroot
│ │ ├── WebSocketManager.js
│ │ └── client.html
├── EchoConsoleClient
│ ├── EchoConsoleClient.csproj
│ └── Program.cs
├── MvcSample
│ ├── Controllers
│ │ └── MessagesController.cs
│ ├── MessageHandlers
│ │ └── NotificationsMessageHandler.cs
│ ├── MvcSample.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Startup.cs
│ └── wwwroot
│ │ └── index.html
├── WebSocketManagerSamples.sln
└── WebTerm
│ ├── ConsoleAppManager.cs
│ ├── NOTICE
│ ├── Program.cs
│ ├── Startup.cs
│ ├── WebTermHandler.cs
│ └── wwwroot
│ ├── FileBrowser.css
│ ├── Index.html
│ ├── jquery.console.js
│ ├── jquery
│ ├── .bower.json
│ ├── LICENSE.txt
│ └── dist
│ │ └── jquery.js
│ └── webterm.js
├── src
├── WebSocketManager.Client.TS
│ ├── WebSocketManager.Client.TS.njsproj
│ ├── dist
│ │ └── WebSocketManager.js
│ ├── package.json
│ ├── src
│ │ ├── Connection.js
│ │ ├── Connection.ts
│ │ ├── InvocationDescriptor.js
│ │ ├── InvocationDescriptor.ts
│ │ ├── Message.js
│ │ └── Message.ts
│ ├── tsconfig.json
│ ├── tslint.json
│ └── webpack.config.js
├── WebSocketManager.Client
│ ├── Connection.cs
│ └── WebSocketManager.Client.csproj
├── WebSocketManager.Common
│ ├── Json
│ │ ├── JsonBinderWithoutAssembly.cs
│ │ └── PrimitiveJsonConverter.cs
│ ├── Networking
│ │ ├── InvocationDescriptor.cs
│ │ ├── InvocationResult.cs
│ │ ├── Message.cs
│ │ └── RemoteException.cs
│ ├── Strategies
│ │ ├── ControllerMethodInvocationStrategy.cs
│ │ ├── DecoratedControllerMethodInvocationStrategy.cs
│ │ ├── MethodInvocationStrategy.cs
│ │ └── StringMethodInvocationStrategy.cs
│ └── WebSocketManager.Common.csproj
└── WebSocketManager
│ ├── WebSocketConnectionManager.cs
│ ├── WebSocketHandler.cs
│ ├── WebSocketManager.csproj
│ ├── WebSocketManagerExtensions.cs
│ └── WebSocketManagerMiddleware.cs
└── test
└── WebSocketManager.Tests
├── Helpers
└── FakeSocket.cs
├── WebSocketConnectionManagerTests.cs
└── WebSocketManager.Tests.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | .vscode/
255 |
256 | src/WebSocketManager.Client.TS/app/build/
257 |
258 | *.map
259 | *.min.js
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | sudo: required
3 | dist: trusty
4 | mono: none
5 | dotnet: 2.0.0
6 |
7 |
8 | addons:
9 | apt:
10 | sources:
11 | - sourceline: 'deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main'
12 | key_url: 'https://apt-mo.trafficmanager.net/keys/microsoft.asc'
13 | packages:
14 | - dotnet-sdk-2.0.0
15 |
16 | before_script:
17 | - dotnet restore
18 | - dotnet restore samples/WebSocketManagerSamples.sln
19 |
20 | script:
21 | - dotnet build
22 | - dotnet build samples/WebSocketManagerSamples.sln
23 | - dotnet test test/WebSocketManager.Tests/WebSocketManager.Tests.csproj
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Radu Matei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # websocket-manager
2 |
3 | Travis: [](https://travis-ci.org/radu-matei/websocket-manager)
4 |
5 | NuGet: [](https://www.nuget.org/packages/WebSocketManager)
6 |
7 | Simple middlware for real-time .NET Core
8 | ----------------------------------------
9 |
10 | This is an Asp .Net Core middleware that provides real-time functionality to .NET Core applications.
11 |
12 | To the core, it is a WebSocket middleware for Asp .Net Core with TypeScript / JavaScript client and .Net Core client that supports the client and the server invoking each others' methods.
13 |
14 | Why wouldn't I use SignalR for this?
15 | ------------------------------------
16 |
17 | First of all, SignalR for Asp .Net Core is still in its very incipient stages. A preview is expected mid-2017, while a release near the end of 2017, so most probably it will be available for Asp .Net Core 2.0.
18 |
19 | > The preview and release information were taken from [this talk by Damian Edwards and David Fowler, the guys in charge of Asp .Net Core](https://vimeo.com/204078084).
20 |
21 | What is this library's connection to SignalR?
22 | ----------------------------------------------
23 |
24 | This library **is not an official release by Microsoft** and in any way related to the original SignalR project, other by a lot of concepts inspired from it.
25 |
26 | Because the release of SignalR for Asp .Net Core was delayed for so long, I decided to write a very basic, stripped down (compared to the original SignalR) that only supports WebSockets (is based on `Microsoft.AspNetCore.WebSockets`) with a TypeScript client.
27 |
28 | A lot of features, both on the server side and the client side were written looking at SignalR (both old and new) code, so if you wrote SignalR in the past, the approach is very similar.
29 |
30 | Get Started with **websocket-manager**
31 | --------------------------------------
32 |
33 | While it is still in development, you can see some examples of usage in the [`samples`](/samples) folder.
34 |
35 | Contribute to **websocket-manager**
36 | -----------------------------------
37 |
38 | Contributions in any form are welcome! Submit issues with bugs and recommendations!
39 | **Pull Requests** are highly appreciated!
--------------------------------------------------------------------------------
/WebSocketManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.9
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketManager.Common", "src\WebSocketManager.Common\WebSocketManager.Common.csproj", "{E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketManager", "src\WebSocketManager\WebSocketManager.csproj", "{633F738C-659D-40A5-B46E-3C9B28F279A6}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketManager.Client", "src\WebSocketManager.Client\WebSocketManager.Client.csproj", "{2D78B356-07F6-421A-9717-B7CD63897263}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSocketManager.Tests", "test\WebSocketManager.Tests\WebSocketManager.Tests.csproj", "{055CA15E-2D9C-4305-AC65-A58CC89F3072}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Debug|x64 = Debug|x64
18 | Debug|x86 = Debug|x86
19 | Release|Any CPU = Release|Any CPU
20 | Release|x64 = Release|x64
21 | Release|x86 = Release|x86
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|x64.ActiveCfg = Debug|Any CPU
27 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|x64.Build.0 = Debug|Any CPU
28 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|x86.ActiveCfg = Debug|Any CPU
29 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Debug|x86.Build.0 = Debug|Any CPU
30 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|x64.ActiveCfg = Release|Any CPU
33 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|x64.Build.0 = Release|Any CPU
34 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|x86.ActiveCfg = Release|Any CPU
35 | {E02806BF-EA9F-4A4C-86ED-68D68BA49ABF}.Release|x86.Build.0 = Release|Any CPU
36 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|x64.ActiveCfg = Debug|Any CPU
39 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|x64.Build.0 = Debug|Any CPU
40 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|x86.ActiveCfg = Debug|Any CPU
41 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Debug|x86.Build.0 = Debug|Any CPU
42 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|x64.ActiveCfg = Release|Any CPU
45 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|x64.Build.0 = Release|Any CPU
46 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|x86.ActiveCfg = Release|Any CPU
47 | {633F738C-659D-40A5-B46E-3C9B28F279A6}.Release|x86.Build.0 = Release|Any CPU
48 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|x64.ActiveCfg = Debug|Any CPU
51 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|x64.Build.0 = Debug|Any CPU
52 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|x86.ActiveCfg = Debug|Any CPU
53 | {2D78B356-07F6-421A-9717-B7CD63897263}.Debug|x86.Build.0 = Debug|Any CPU
54 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|x64.ActiveCfg = Release|Any CPU
57 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|x64.Build.0 = Release|Any CPU
58 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|x86.ActiveCfg = Release|Any CPU
59 | {2D78B356-07F6-421A-9717-B7CD63897263}.Release|x86.Build.0 = Release|Any CPU
60 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|x64.ActiveCfg = Debug|Any CPU
63 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|x64.Build.0 = Debug|Any CPU
64 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|x86.ActiveCfg = Debug|Any CPU
65 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Debug|x86.Build.0 = Debug|Any CPU
66 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|x64.ActiveCfg = Release|Any CPU
69 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|x64.Build.0 = Release|Any CPU
70 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|x86.ActiveCfg = Release|Any CPU
71 | {055CA15E-2D9C-4305-AC65-A58CC89F3072}.Release|x86.Build.0 = Release|Any CPU
72 | EndGlobalSection
73 | GlobalSection(SolutionProperties) = preSolution
74 | HideSolutionNode = FALSE
75 | EndGlobalSection
76 | GlobalSection(MonoDevelopProperties) = preSolution
77 | Policies = $0
78 | $0.StandardHeader = $1
79 | version = 0.2
80 | EndGlobalSection
81 | EndGlobal
82 |
--------------------------------------------------------------------------------
/samples/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/radu-matei/websocket-manager/7c62da3643a562b4a441ce095f15a6557f76e9ee/samples/.DS_Store
--------------------------------------------------------------------------------
/samples/ChatApplication/ChatApplication.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 | 0.2
6 |
7 |
8 |
9 | ..\..\..\websocke t-manager\samples\ChatApplication\bin\Debug\netcoreapp2.0
10 |
11 |
12 | ..\..\..\websocke t-manager\samples\ChatApplication\bin\Release\netcoreapp2.0
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/samples/ChatApplication/ChatHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.WebSockets;
3 | using System.Threading.Tasks;
4 | using WebSocketManager;
5 | using WebSocketManager.Common;
6 |
7 | namespace ChatApplication
8 | {
9 | public class ChatHandler : WebSocketHandler
10 | {
11 | public ChatHandler(WebSocketConnectionManager webSocketConnectionManager) : base(webSocketConnectionManager, new ControllerMethodInvocationStrategy())
12 | {
13 | ((ControllerMethodInvocationStrategy)MethodInvocationStrategy).Controller = this;
14 | }
15 |
16 | public override async Task OnConnected(WebSocket socket)
17 | {
18 | await base.OnConnected(socket);
19 |
20 | var socketId = WebSocketConnectionManager.GetId(socket);
21 |
22 | var message = new Message()
23 | {
24 | MessageType = MessageType.Text,
25 | Data = $"{socketId} is now connected"
26 | };
27 |
28 | await SendMessageToAllAsync(message);
29 | }
30 |
31 | // this method can be called from a client, doesn't return anything.
32 | public async Task SendMessage(WebSocket socket, string message)
33 | {
34 | // chat command.
35 | if (message == "/math")
36 | {
37 | await AskClientToDoMath(socket);
38 | }
39 | else
40 | {
41 | await InvokeClientMethodToAllAsync("receiveMessage", WebSocketConnectionManager.GetId(socket), message);
42 | }
43 | }
44 |
45 | // this method can be called from a client, returns the integer result or throws an exception.
46 | public int DoMath(WebSocket socket, int a, int b)
47 | {
48 | if (a == 0 || b == 0) throw new Exception("That makes no sense.");
49 | return a + b;
50 | }
51 |
52 | // we ask a client to do some math for us then broadcast the results.
53 | private async Task AskClientToDoMath(WebSocket socket)
54 | {
55 | string id = WebSocketConnectionManager.GetId(socket);
56 | try
57 | {
58 | int result = await InvokeClientMethodAsync(id, "DoMath", 3, 5);
59 | await InvokeClientMethodOnlyAsync(id, "receiveMessage", "Server", $"You sent me this result: " + result);
60 | }
61 | catch (Exception ex)
62 | {
63 | await InvokeClientMethodOnlyAsync(id, "receiveMessage", "Server", $"I had an exception: " + ex.Message);
64 | }
65 | }
66 |
67 | public override async Task OnDisconnected(WebSocket socket)
68 | {
69 | var socketId = WebSocketConnectionManager.GetId(socket);
70 |
71 | await base.OnDisconnected(socket);
72 |
73 | var message = new Message()
74 | {
75 | MessageType = MessageType.Text,
76 | Data = $"{socketId} disconnected"
77 | };
78 | await SendMessageToAllAsync(message);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/samples/ChatApplication/Program.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.AspNetCore;
4 |
5 | namespace ChatApplication
6 | {
7 | public class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | BuildWebHost(args).Run();
12 | }
13 |
14 | public static IWebHost BuildWebHost(string[] args)
15 | {
16 | return WebHost.CreateDefaultBuilder(args)
17 | .UseStartup()
18 | .Build();
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/ChatApplication/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:65109/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "ChatApplication": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:65110"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/samples/ChatApplication/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | using WebSocketManager;
6 |
7 | namespace ChatApplication
8 | {
9 | public class Startup
10 | {
11 | public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
12 | {
13 | app.UseWebSockets();
14 | app.MapWebSocketManager("/chat", serviceProvider.GetService());
15 |
16 | app.UseStaticFiles();
17 | }
18 |
19 | public void ConfigureServices(IServiceCollection services)
20 | {
21 | services.AddWebSocketManager();
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/samples/ChatApplication/wwwroot/WebSocketManager.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The WebSocketManager JavaScript Client. See https://github.com/radu-matei/websocket-manager/ for more information.
3 | */
4 | var WebSocketManager = (function () {
5 | /**
6 | * Create a new web socket manager.
7 | * @param {any} url The web socket url (must start with ws://).
8 | */
9 | var constructor = function (url) {
10 | if (url === undefined) console.error("WebSocketManager constructor requires valid 'url'.");
11 | _this = this;
12 |
13 | /** Collection of methods on this client. */
14 | this.methods = [];
15 |
16 | ///////////////////////////////////////////////////////////////////////////////////////////
17 |
18 | /**
19 | * Create a new networking message.
20 | */
21 | var Message = function (messageType, data) {
22 | this.$type = 'WebSocketManager.Common.Message';
23 | this.messageType = messageType;
24 | this.data = data;
25 | };
26 | /** Text message (constant: 0). */
27 | Message.Text = 0;
28 | /** Remote method invocation request message (constant: 1). */
29 | Message.MethodInvocation = 1;
30 | /** Connection event message (constant: 2). */
31 | Message.ConnectionEvent = 2;
32 | /** Remote method return value message (constant: 3). */
33 | Message.MethodReturnValue = 3;
34 |
35 | ///////////////////////////////////////////////////////////////////////////////////////////
36 |
37 | /**
38 | * Create a new invocation descriptor.
39 | * @param {any} methodName The name of the remote method.
40 | * @param {any} args The arguments passed to the method.
41 | * @param {any} identifier The unique identifier of the invocation.
42 | */
43 | var InvocationDescriptor = function (methodName, args, identifier) {
44 | this.$type = 'WebSocketManager.Common.InvocationDescriptor';
45 | this.methodName = methodName;
46 | this.arguments = {
47 | $type: 'System.Object[]',
48 | $values: args
49 | };
50 | this.identifier = {
51 | $type: "System.Guid",
52 | $value: identifier
53 | };
54 | };
55 |
56 | ///////////////////////////////////////////////////////////////////////////////////////////
57 |
58 | /**
59 | * Represents the return value of a method that was executed remotely.
60 | * @param {any} identifier The unique identifier of the invocation.
61 | * @param {any} result The result of the method call.
62 | * @param {any} exception The remote exception of the method call.
63 | */
64 | var InvocationResult = function (identifier, result, exception) {
65 | this.$type = 'WebSocketManager.Common.InvocationResult';
66 | this.result = result;
67 | this.exception = exception;
68 | this.identifier = {
69 | $type: "System.Guid",
70 | $value: identifier
71 | };
72 | if (exception !== undefined) {
73 | this.exception = {
74 | $type: "WebSocketManager.Common.RemoteException",
75 | message: exception
76 | }
77 | }
78 | };
79 |
80 | ///////////////////////////////////////////////////////////////////////////////////////////
81 |
82 | /**
83 | * Collection of primitive type names and their C# mappings.
84 | */
85 | var typemappings = {
86 | guid: 'System.Guid',
87 | uuid: 'System.Guid', // convenience alias
88 | bool: 'System.Boolean',
89 | byte: 'System.Byte',
90 | sbyte: 'System.SByte',
91 | char: 'System.Char',
92 | decimal: 'System.Decimal',
93 | double: 'System.Double',
94 | float: 'System.Single',
95 | int: 'System.Int32',
96 | uint: 'System.UInt32',
97 | long: 'System.Int64',
98 | ulong: 'System.UInt64',
99 | short: 'System.Int16',
100 | ushort: 'System.UInt16',
101 | string: 'System.String',
102 | object: 'System.Object' // generic
103 | };
104 |
105 | ///////////////////////////////////////////////////////////////////////////////////////////
106 |
107 | /**
108 | * Generates a UUID using a random number generator and the current time.
109 | * This is not truly unique but it's good enough (TM).
110 | */
111 | var uuid = function () { // Public Domain/MIT
112 | var d = new Date().getTime();
113 | if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
114 | d += performance.now(); // use high-precision timer if available
115 | }
116 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
117 | var r = (d + Math.random() * 16) % 16 | 0;
118 | d = Math.floor(d / 16);
119 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
120 | });
121 | }
122 |
123 | ///////////////////////////////////////////////////////////////////////////////////////////
124 |
125 | /**
126 | * Takes a C# collection of $type+$value and turns them into a simple array of values.
127 | * @param {any} collection The C# collection of $type+$value.
128 | */
129 | var parseCSharpArguments = function (collection) {
130 | var args = [];
131 | for (var i = 0; i < collection.length; i++)
132 | args.push(collection[i].$value);
133 | return args;
134 | }
135 |
136 | ///////////////////////////////////////////////////////////////////////////////////////////
137 |
138 | /**
139 | * The waiting remote invocations for Client to Server method calls (return values).
140 | */
141 | var waitingRemoteInvocations = {};
142 |
143 | ///////////////////////////////////////////////////////////////////////////////////////////
144 |
145 | /**
146 | * Called whenever the socket opens the connection.
147 | * @param {any} event The associated event data.
148 | */
149 | var onSocketOpen = function (event) {
150 | };
151 |
152 | /**
153 | * Called whenever the socket closes the connection.
154 | * @param {any} event The associated event data.
155 | */
156 | var onSocketClose = function (event) {
157 | // public event:
158 | if (_this.onDisconnected !== undefined) _this.onDisconnected();
159 | };
160 |
161 | /**
162 | * Called whenever the socket has an error.
163 | * @param {any} event The associated event data.
164 | */
165 | var onSocketError = function (event) {
166 | console.error("WebSocketManager error:");
167 | console.error(event);
168 | };
169 |
170 | /**
171 | * Called whenever there is an incoming message.
172 | * @param {any} message The Message that was received.
173 | */
174 | var onSocketMessage = function (message) {
175 | // CONNECTION EVENT
176 | if (message.messageType === Message.ConnectionEvent) {
177 | // we received the unique identifier from the server.
178 | _this.id = message.data.$value;
179 | // public event:
180 | if (_this.onConnected !== undefined) _this.onConnected(_this.id);
181 | }
182 |
183 | // TEXT EVENT
184 | else if (message.messageType === Message.Text) {
185 | // public event:
186 | if (_this.onMessage !== undefined) _this.onMessage(message.data.$value);
187 | }
188 |
189 | // METHOD INVOCATION EVENT
190 | else if (message.messageType === Message.MethodInvocation) {
191 | var data = JSON.parse(message.data.$value);
192 | // find the method.
193 | if (_this.methods[data.methodName.$value] !== undefined) {
194 | // call the method and catch any exceptions.
195 | var result, error = undefined;
196 | try { result = _this.methods[data.methodName.$value].apply(_this, parseCSharpArguments(data.arguments['$values'])); }
197 | catch (e) { error = e; }
198 |
199 | // if the server desires a result we send a method return value.
200 | if (data.identifier.$value !== '00000000-0000-0000-0000-000000000000') {
201 | // an error occured so let the server know.
202 | if (error !== undefined) {
203 | // send web-socket message to the server.
204 | _this.socket.send(JSON.stringify(new Message(Message.MethodReturnValue,
205 | JSON.stringify(new InvocationResult(data.identifier.$value, null, "A remote exception occured: " + error))
206 | )));
207 | }
208 | // send result value to the server.
209 | else {
210 | // try finding an appropriate C# type.
211 | if (typemappings[result[0]] !== undefined)
212 | result[0] = typemappings[result[0]];
213 | // send web-socket message to the server.
214 | _this.socket.send(JSON.stringify(new Message(Message.MethodReturnValue,
215 | JSON.stringify(new InvocationResult(data.identifier.$value, { $type: result[0], $value: result[1] }))
216 | )));
217 | }
218 | }
219 | } else console.error("WebSocketManager: Server attempted to invoke unknown method '" + data.methodName.$value + "'!");
220 | }
221 |
222 | // METHOD RETURN VALUE EVENT
223 | else if (message.messageType === Message.MethodReturnValue) {
224 | var data = JSON.parse(message.data.$value);
225 | // find the waiting remote invocation.
226 | var callback = waitingRemoteInvocations[data.identifier.$value];
227 | // remove it from the waiting list.
228 | delete waitingRemoteInvocations[data.identifier.$value];
229 | // call the callback.
230 | if (data.exception !== null)
231 | callback(undefined, data.exception.message.$value);
232 | else
233 | callback(data.result.$value, undefined);
234 | }
235 |
236 | //console.log(message);
237 | };
238 |
239 | ///////////////////////////////////////////////////////////////////////////////////////////
240 |
241 | /**
242 | * Connects to the server.
243 | */
244 | this.connect = function () {
245 | // create a new web-socket connection to the server.
246 | _this.socket = new WebSocket(url);
247 |
248 | _this.socket.onopen = function (event) {
249 | onSocketOpen(event);
250 | }
251 |
252 | _this.socket.onclose = function (event) {
253 | // run all the callbacks on the waiting list so the program continues.
254 | Object.keys(waitingRemoteInvocations).forEach(function (guid) {
255 | waitingRemoteInvocations[guid](undefined, 'The web-socket connection was closed.');
256 | });
257 | waitingRemoteInvocations = {};
258 |
259 | onSocketClose(event);
260 | }
261 |
262 | _this.socket.onerror = function (event) {
263 | onSocketError(event);
264 | }
265 |
266 | _this.socket.onmessage = function (event) {
267 | onSocketMessage(JSON.parse(event.data));
268 | }
269 | };
270 |
271 | /**
272 | * Invoke a remote method on the server only, without a return value.
273 | * @param {any} method The name of the remote method to be invoked.
274 | */
275 | this.invokeOnly = function (method) {
276 | var args = [];
277 | // iterate through all arguments and find type/value relationships.
278 | for (var i = 1; i < arguments.length; i += 2) {
279 | var type = arguments[i];
280 | var value = arguments[i + 1];
281 | // try finding an appropriate C# type.
282 | if (typemappings[type] !== undefined)
283 | type = typemappings[type];
284 | // even if we can't find a C# type we assume the user knows what he's doing.
285 | args.push({ $type: type, $value: value });
286 | }
287 |
288 | // send web-socket message to the server.
289 | _this.socket.send(JSON.stringify(new Message(Message.MethodInvocation,
290 | JSON.stringify(new InvocationDescriptor(method, args, '00000000-0000-0000-0000-000000000000'))
291 | )));
292 | }
293 |
294 | /**
295 | * Invoke a remote method on the server, with a callback for the return value.
296 | * @param {any} method The name of the remote method to be invoked.
297 | */
298 | this.invoke = function (method) {
299 | var args = [];
300 | // iterate through all arguments and find type/value relationships.
301 | for (var i = 1; i < arguments.length - 1; i += 2) {
302 | var type = arguments[i];
303 | var value = arguments[i + 1];
304 | // try finding an appropriate C# type.
305 | if (typemappings[type] !== undefined)
306 | type = typemappings[type];
307 | // even if we can't find a C# type we assume the user knows what he's doing.
308 | args.push({ $type: type, $value: value });
309 | }
310 | // the last argument should be the callback method.
311 | var callback = arguments[arguments.length - 1];
312 | // generate a unique identifier to associate return values.
313 | var guid = uuid();
314 | // put this call on the waiting list.
315 | waitingRemoteInvocations[guid] = callback;
316 |
317 | // send web-socket message to the server.
318 | _this.socket.send(JSON.stringify(new Message(Message.MethodInvocation,
319 | JSON.stringify(new InvocationDescriptor(method, args, guid))
320 | )));
321 | }
322 | };
323 |
324 | return constructor;
325 | })();
--------------------------------------------------------------------------------
/samples/ChatApplication/wwwroot/client.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test Page
7 |
8 |
9 |
10 | This should be mapped to "/chat"
11 |
12 | Send
13 |
14 |
15 |
16 |
17 |
18 |
19 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/samples/EchoConsoleClient/EchoConsoleClient.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 | 0.2
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/samples/EchoConsoleClient/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using WebSocketManager.Client;
4 | using WebSocketManager.Common;
5 |
6 | public class Program
7 | {
8 | private static Connection _connection;
9 | private static StringMethodInvocationStrategy _strategy;
10 |
11 | public static void Main(string[] args)
12 | {
13 | StartConnectionAsync();
14 |
15 | _strategy.On("receiveMessage", (arguments) =>
16 | {
17 | Console.WriteLine($"{arguments[0]} said: {arguments[1]}");
18 | });
19 |
20 | Console.ReadLine();
21 | StopConnectionAsync();
22 | }
23 |
24 | public static async Task StartConnectionAsync()
25 | {
26 | _strategy = new StringMethodInvocationStrategy();
27 | _connection = new Connection(_strategy);
28 | await _connection.StartConnectionAsync("ws://localhost:65110/chat");
29 | }
30 |
31 | public static async Task StopConnectionAsync()
32 | {
33 | await _connection.StopConnectionAsync();
34 | }
35 | }
--------------------------------------------------------------------------------
/samples/MvcSample/Controllers/MessagesController.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Microsoft.AspNetCore.Mvc;
3 | using MvcSample.MessageHandlers;
4 |
5 | namespace MvcSample.Controllers
6 | {
7 | public class MessagesController : Controller
8 | {
9 | private NotificationsMessageHandler _notificationsMessageHandler { get; set; }
10 |
11 | public MessagesController(NotificationsMessageHandler notificationsMessageHandler)
12 | {
13 | _notificationsMessageHandler = notificationsMessageHandler;
14 | }
15 |
16 | [HttpGet]
17 | public async Task SendMessage([FromQueryAttribute]string message)
18 | {
19 | await _notificationsMessageHandler.InvokeClientMethodToAllAsync("receiveMessage", message);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/samples/MvcSample/MessageHandlers/NotificationsMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using WebSocketManager;
2 | using WebSocketManager.Common;
3 |
4 | namespace MvcSample.MessageHandlers
5 | {
6 | public class NotificationsMessageHandler : WebSocketHandler
7 | {
8 | public NotificationsMessageHandler(WebSocketConnectionManager webSocketConnectionManager) : base(webSocketConnectionManager, new StringMethodInvocationStrategy())
9 | {
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/samples/MvcSample/MvcSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 | 0.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/MvcSample/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.AspNetCore;
3 |
4 | namespace MvcSample
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | BuildWebHost(args).Run();
11 | }
12 |
13 | public static IWebHost BuildWebHost(string[] args)
14 | {
15 | return WebHost.CreateDefaultBuilder(args)
16 | .UseStartup()
17 | .Build();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/samples/MvcSample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:65124/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "MvcSample": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:65125"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/samples/MvcSample/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using MvcSample.MessageHandlers;
5 | using WebSocketManager;
6 |
7 | namespace MvcSample
8 | {
9 | public class Startup
10 | {
11 | public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
12 | {
13 | app.UseStaticFiles();
14 | app.UseWebSockets();
15 |
16 | app.UseMvc(routes =>
17 | {
18 | routes.MapRoute(
19 | name: "default",
20 | template: "api/{controller}/{action}/{id?}"
21 | );
22 | });
23 |
24 | app.MapWebSocketManager("/notifications", serviceProvider.GetService());
25 | }
26 |
27 | public void ConfigureServices(IServiceCollection services)
28 | {
29 | services.AddMvc();
30 | services.AddWebSocketManager();
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/samples/MvcSample/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Real-Time Notifications
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | This should be mapped to "/notifications"
17 |
18 | Send
19 |
20 |
21 |
22 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/samples/WebSocketManagerSamples.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27130.2036
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatApplication", "ChatApplication\ChatApplication.csproj", "{C64533FC-4066-438D-9297-BDF1150B3F9A}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoConsoleClient", "EchoConsoleClient\EchoConsoleClient.csproj", "{DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcSample", "MvcSample\MvcSample.csproj", "{89A1D156-DE58-4C80-B8C3-B1B389764D95}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSocketManager", "..\src\WebSocketManager\WebSocketManager.csproj", "{9D92BAD3-058A-452C-8701-C0E7C3AE268E}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSocketManager.Common", "..\src\WebSocketManager.Common\WebSocketManager.Common.csproj", "{2A5BA455-1A0F-4B36-998D-0FBDBCF69137}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSocketManager.Client", "..\src\WebSocketManager.Client\WebSocketManager.Client.csproj", "{818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Debug|x64 = Debug|x64
22 | Debug|x86 = Debug|x86
23 | Release|Any CPU = Release|Any CPU
24 | Release|x64 = Release|x64
25 | Release|x86 = Release|x86
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|x64.ActiveCfg = Debug|Any CPU
31 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|x64.Build.0 = Debug|Any CPU
32 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|x86.ActiveCfg = Debug|Any CPU
33 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Debug|x86.Build.0 = Debug|Any CPU
34 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|x64.ActiveCfg = Release|Any CPU
37 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|x64.Build.0 = Release|Any CPU
38 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|x86.ActiveCfg = Release|Any CPU
39 | {C64533FC-4066-438D-9297-BDF1150B3F9A}.Release|x86.Build.0 = Release|Any CPU
40 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|x64.ActiveCfg = Debug|Any CPU
43 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|x64.Build.0 = Debug|Any CPU
44 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|x86.ActiveCfg = Debug|Any CPU
45 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Debug|x86.Build.0 = Debug|Any CPU
46 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|x64.ActiveCfg = Release|Any CPU
49 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|x64.Build.0 = Release|Any CPU
50 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|x86.ActiveCfg = Release|Any CPU
51 | {DDE2DC69-2CD5-4C4F-98D3-3609942A6E63}.Release|x86.Build.0 = Release|Any CPU
52 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|x64.ActiveCfg = Debug|Any CPU
55 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|x64.Build.0 = Debug|Any CPU
56 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|x86.ActiveCfg = Debug|Any CPU
57 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Debug|x86.Build.0 = Debug|Any CPU
58 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|x64.ActiveCfg = Release|Any CPU
61 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|x64.Build.0 = Release|Any CPU
62 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|x86.ActiveCfg = Release|Any CPU
63 | {89A1D156-DE58-4C80-B8C3-B1B389764D95}.Release|x86.Build.0 = Release|Any CPU
64 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|x64.ActiveCfg = Debug|Any CPU
67 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|x64.Build.0 = Debug|Any CPU
68 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|x86.ActiveCfg = Debug|Any CPU
69 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Debug|x86.Build.0 = Debug|Any CPU
70 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|x64.ActiveCfg = Release|Any CPU
73 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|x64.Build.0 = Release|Any CPU
74 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|x86.ActiveCfg = Release|Any CPU
75 | {9D92BAD3-058A-452C-8701-C0E7C3AE268E}.Release|x86.Build.0 = Release|Any CPU
76 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
77 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|Any CPU.Build.0 = Debug|Any CPU
78 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|x64.ActiveCfg = Debug|Any CPU
79 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|x64.Build.0 = Debug|Any CPU
80 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|x86.ActiveCfg = Debug|Any CPU
81 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Debug|x86.Build.0 = Debug|Any CPU
82 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|Any CPU.ActiveCfg = Release|Any CPU
83 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|Any CPU.Build.0 = Release|Any CPU
84 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|x64.ActiveCfg = Release|Any CPU
85 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|x64.Build.0 = Release|Any CPU
86 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|x86.ActiveCfg = Release|Any CPU
87 | {2A5BA455-1A0F-4B36-998D-0FBDBCF69137}.Release|x86.Build.0 = Release|Any CPU
88 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
89 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
90 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|x64.ActiveCfg = Debug|Any CPU
91 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|x64.Build.0 = Debug|Any CPU
92 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|x86.ActiveCfg = Debug|Any CPU
93 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Debug|x86.Build.0 = Debug|Any CPU
94 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
95 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|Any CPU.Build.0 = Release|Any CPU
96 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|x64.ActiveCfg = Release|Any CPU
97 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|x64.Build.0 = Release|Any CPU
98 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|x86.ActiveCfg = Release|Any CPU
99 | {818BF6BF-9F97-47E5-8AFC-77E3EDA2A5C9}.Release|x86.Build.0 = Release|Any CPU
100 | EndGlobalSection
101 | GlobalSection(SolutionProperties) = preSolution
102 | HideSolutionNode = FALSE
103 | EndGlobalSection
104 | GlobalSection(ExtensibilityGlobals) = postSolution
105 | SolutionGuid = {792302F2-9E03-46AD-B5B7-F3391E84E1D3}
106 | EndGlobalSection
107 | GlobalSection(MonoDevelopProperties) = preSolution
108 | version = 0.2
109 | EndGlobalSection
110 | EndGlobal
111 |
--------------------------------------------------------------------------------
/samples/WebTerm/ConsoleAppManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebTerm
9 | {
10 | // taken from http://stackoverflow.com/questions/21848271/redirecting-standard-input-of-console-application and slightly modified
11 | public class ConsoleAppManager
12 | {
13 | private readonly string appName;
14 | private readonly Process process = new Process();
15 | private readonly object theLock = new object();
16 | private SynchronizationContext context;
17 | private string pendingWriteData;
18 |
19 | public ConsoleAppManager(string appName)
20 | {
21 | this.appName = appName;
22 |
23 | this.process.StartInfo.FileName = this.appName;
24 | this.process.StartInfo.RedirectStandardError = true;
25 | this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
26 |
27 | this.process.StartInfo.RedirectStandardInput = true;
28 | this.process.StartInfo.RedirectStandardOutput = true;
29 | this.process.EnableRaisingEvents = true;
30 | this.process.StartInfo.CreateNoWindow = true;
31 |
32 | this.process.StartInfo.UseShellExecute = false;
33 |
34 | this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
35 |
36 | this.process.Exited += this.ProcessOnExited;
37 | }
38 |
39 | public event EventHandler ErrorTextReceived;
40 | public event EventHandler ProcessExited;
41 | public event EventHandler StandartTextReceived;
42 |
43 | public int ExitCode
44 | {
45 | get { return this.process.ExitCode; }
46 | }
47 |
48 | public bool Running
49 | {
50 | get; private set;
51 | }
52 |
53 | public void ExecuteAsync(params string[] args)
54 | {
55 | if (this.Running)
56 | {
57 | throw new InvalidOperationException(
58 | "Process is still Running. Please wait for the process to complete.");
59 | }
60 |
61 | string arguments = string.Join(" ", args);
62 |
63 | this.process.StartInfo.Arguments = arguments;
64 |
65 | this.context = SynchronizationContext.Current;
66 |
67 | this.process.Start();
68 | this.Running = true;
69 |
70 | new Task(this.ReadOutputAsync).Start();
71 | new Task(this.WriteInputTask).Start();
72 | new Task(this.ReadOutputErrorAsync).Start();
73 | }
74 |
75 | public void Write(string data)
76 | {
77 | if (data == null)
78 | {
79 | return;
80 | }
81 |
82 | lock (this.theLock)
83 | {
84 | this.pendingWriteData = data;
85 | }
86 | }
87 |
88 | public void WriteLine(string data)
89 | {
90 | this.Write(data + Environment.NewLine);
91 | }
92 |
93 | protected virtual void OnErrorTextReceived(string e)
94 | {
95 | EventHandler handler = this.ErrorTextReceived;
96 |
97 | if (handler != null)
98 | {
99 | if (this.context != null)
100 | {
101 | this.context.Post(delegate { handler(this, e); }, null);
102 | }
103 | else
104 | {
105 | handler(this, e);
106 | }
107 | }
108 | }
109 |
110 | protected virtual void OnProcessExited()
111 | {
112 | EventHandler handler = this.ProcessExited;
113 | if (handler != null)
114 | {
115 | handler(this, EventArgs.Empty);
116 | }
117 | }
118 |
119 | protected virtual void OnStandartTextReceived(string e)
120 | {
121 | EventHandler handler = this.StandartTextReceived;
122 |
123 | if (handler != null)
124 | {
125 | if (this.context != null)
126 | {
127 | this.context.Post(delegate { handler(this, e); }, null);
128 | }
129 | else
130 | {
131 | handler(this, e);
132 | }
133 | }
134 | }
135 |
136 | private void ProcessOnExited(object sender, EventArgs eventArgs)
137 | {
138 | this.OnProcessExited();
139 | }
140 |
141 | private async void ReadOutputAsync()
142 | {
143 | var standart = new StringBuilder();
144 | var buff = new char[1024];
145 | int length;
146 |
147 | while (this.process.HasExited == false)
148 | {
149 | standart.Clear();
150 |
151 | length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
152 | standart.Append(buff.SubArray(0, length));
153 | this.OnStandartTextReceived(standart.ToString());
154 | Thread.Sleep(1);
155 | }
156 |
157 | this.Running = false;
158 | }
159 |
160 | private async void ReadOutputErrorAsync()
161 | {
162 | var sb = new StringBuilder();
163 |
164 | do
165 | {
166 | sb.Clear();
167 | var buff = new char[1024];
168 | int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
169 | sb.Append(buff.SubArray(0, length));
170 | this.OnErrorTextReceived(sb.ToString());
171 | Thread.Sleep(1);
172 | }
173 | while (this.process.HasExited == false);
174 | }
175 |
176 | private async void WriteInputTask()
177 | {
178 | while (this.process.HasExited == false)
179 | {
180 | Thread.Sleep(1);
181 |
182 | if (this.pendingWriteData != null)
183 | {
184 | await this.process.StandardInput.WriteAsync(this.pendingWriteData);
185 | await this.process.StandardInput.FlushAsync();
186 |
187 | lock (this.theLock)
188 | {
189 | this.pendingWriteData = null;
190 | }
191 | }
192 | }
193 | }
194 |
195 | public void Kill() {
196 | this.process.Kill();
197 | }
198 | }
199 |
200 | public static class CharArrayExtensions
201 | {
202 | public static char[] SubArray(this char[] input, int startIndex, int length)
203 | {
204 | List result = new List();
205 | for (int i = startIndex; i < length; i++)
206 | {
207 | result.Add(input[i]);
208 | }
209 |
210 | return result.ToArray();
211 | }
212 | }
213 | }
--------------------------------------------------------------------------------
/samples/WebTerm/NOTICE:
--------------------------------------------------------------------------------
1 | Webterm Project
2 |
3 | Webterm includes works distributed under the licenses listed below. The full text for most of the licenses listed below can be found in the LICENSE.txt file accompanying each work. The original copyright notices have been preserved within the respective files and or packages. Please refer to the specific files and/or packages for more detailed information about the authors, copyright notices, and licenses.
4 |
5 | Kudu
6 | -----
7 | Website: https://github.com/projectkudu/kudu/
8 | Copyright: Copyright 2015 .NET Foundation
9 | License: Apache 2.0
10 | Modifications:webterm.js is a modified version of KuduExecV2.js
11 |
12 | jQuery
13 | -----
14 | Website: http://jquery.com
15 | Copyright: Copyright (c) 2010 John Resig, http://jquery.com
16 | License: MIT
17 |
18 | jQuery jquery-console
19 | -----
20 | Website: https://github.com/chrisdone/jquery-console
21 | Copyright: Copyright 2010 Chris Done, Simon David Pratt. All rights reserved.
22 | License: http://github.com/chrisdone/jquery-console/blob/master/README.md
23 |
24 | Json.NET
25 | -----
26 | Website: http://json.codeplex.com/
27 | Copyright: Copyright (c) 2007 James Newton-King
28 | License: MIT
--------------------------------------------------------------------------------
/samples/WebTerm/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Microsoft.AspNetCore.Hosting;
4 |
5 | namespace WebTerm {
6 | public class Program {
7 | public static void Main(string[] args) {
8 | var host = new WebHostBuilder()
9 | .UseKestrel()
10 | .UseContentRoot(Directory.GetCurrentDirectory())
11 | .UseStartup()
12 | .Build();
13 |
14 | host.Run();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/WebTerm/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | using WebSocketManager;
6 |
7 | namespace WebTerm {
8 | public class Startup {
9 | public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider) {
10 | app.UseWebSockets();
11 | app.MapWebSocketManager("/cmd", serviceProvider.GetService());
12 |
13 | app.UseStaticFiles();
14 | }
15 |
16 | public void ConfigureServices(IServiceCollection services) {
17 | services.AddWebSocketManager();
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/samples/WebTerm/WebTermHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.WebSockets;
4 | using System.Threading.Tasks;
5 | using Newtonsoft.Json;
6 | using WebSocketManager;
7 | using WebSocketManager.Common;
8 |
9 | namespace WebTerm {
10 | public class WebTermHandler : WebSocketHandler {
11 | private Dictionary socketIdToProcess = new Dictionary();
12 | private Dictionary processToSocketId = new Dictionary();
13 |
14 | public WebTermHandler(WebSocketConnectionManager webSocketConnectionManager)
15 | : base(webSocketConnectionManager) {
16 | }
17 |
18 | private void Mgr_ErrorTextReceived(object sender, string e) {
19 | Console.WriteLine("e>>>" + e + "<<>>exit<<<");
28 | }
29 |
30 | private void Mgr_StandartTextReceived(object sender, string e) {
31 | Console.WriteLine(">>>" + e + "<<<");
32 | string socketId;
33 | if (processToSocketId.TryGetValue(sender as ConsoleAppManager, out socketId)) {
34 | SendToClient(socketId, e, null).Wait();
35 | }
36 | }
37 |
38 | public override async Task OnConnected(WebSocket socket) {
39 | await base.OnConnected(socket);
40 |
41 | var socketId = WebSocketConnectionManager.GetId(socket);
42 |
43 | var message = new Message() {
44 | MessageType = MessageType.Text,
45 | Data = "{ \"Output\": \"connected\", \"Error\": \"\" }"
46 | };
47 |
48 | System.Console.WriteLine("new connection");
49 |
50 | var mgr = new ConsoleAppManager("cmd.exe");
51 | socketIdToProcess[socketId] = mgr;
52 | processToSocketId[mgr] = socketId;
53 | mgr.StandartTextReceived += Mgr_StandartTextReceived;
54 | mgr.ErrorTextReceived += Mgr_ErrorTextReceived;
55 | mgr.ProcessExited += Mgr_ProcessExited;
56 |
57 | mgr.ExecuteAsync("/Q");
58 |
59 | await SendMessageToAllAsync(message);
60 | }
61 |
62 | public async Task ReceiveMessage(string socketId, string input) {
63 | System.Console.WriteLine($"ReceiveMessage ({socketId}): {input}");
64 | socketIdToProcess[socketId].Write(input);
65 | }
66 |
67 | public async Task SendToClient(string socketId, string output, string error) {
68 | System.Console.WriteLine($"SendToClient ({socketId}): {output}");
69 | await InvokeClientMethodAsync(socketId, "receiveMessage", new object[] {
70 | new {
71 | Output = output,
72 | Error = error
73 | }}
74 | );
75 | }
76 |
77 | public override async Task OnDisconnected(WebSocket socket) {
78 | var socketId = WebSocketConnectionManager.GetId(socket);
79 |
80 | await base.OnDisconnected(socket);
81 |
82 | var message = new Message() {
83 | MessageType = MessageType.Text,
84 | Data = "{'Output': 'disconnected', 'Error': ''}"
85 | };
86 |
87 | var mgr = socketIdToProcess[socketId];
88 | mgr.Kill();
89 | processToSocketId.Remove(mgr);
90 | socketIdToProcess.Remove(socketId);
91 |
92 | System.Console.WriteLine("disconnected");
93 | await SendMessageToAllAsync(message);
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/samples/WebTerm/wwwroot/FileBrowser.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | #webtermConsole {
8 | height: 80%;
9 | width: 97%;
10 | }
11 |
12 | div.console {
13 | font-family: Consolas, 'Lucida Console', 'Courier New', monospace;
14 | height: 100%;
15 | margin: auto;
16 | white-space: pre-wrap;
17 | width: 100%;
18 | }
19 |
20 | div.console div.jquery-console-inner {
21 | background-color: black;
22 | color: white;
23 | height: 100%;
24 | margin-left: auto;
25 | margin-right: auto;
26 | overflow: auto;
27 | word-break: break-all;
28 | word-wrap: break-word;
29 | padding: 0.3em;
30 | width: 100%;
31 | }
32 |
33 | div.console div.jquery-console-focus span.jquery-console-cursor {
34 | background: #5bc0de;
35 | color: black;
36 | animation: blink 1s steps(5, start) infinite;
37 | -webkit-animation: blink 1s steps(5, start) infinite;
38 | }
39 |
40 | @-webkit-keyframes blink {
41 | to {
42 | visibility: hidden;
43 | }
44 | }
45 |
46 | @keyframes blink {
47 | to {
48 | visibility: hidden;
49 | }
50 | }
51 |
52 | div.console div.jquery-console-message-error {
53 | color: red;
54 | }
55 |
56 | div.console div.jquery-console-message-value {
57 | color: white;
58 | }
59 |
60 | div.console div.jquery-console-message-type {
61 | color: white;
62 | }
63 |
64 | div.console span.jquery-console-prompt-pid {
65 | color: green;
66 | }
67 |
--------------------------------------------------------------------------------
/samples/WebTerm/wwwroot/Index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Webterm
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/WebTerm/wwwroot/jquery.console.js:
--------------------------------------------------------------------------------
1 | // JQuery Console 1.0
2 | // Sun Feb 21 20:28:47 GMT 2010
3 | //
4 | // Copyright 2010 Chris Done, Simon David Pratt. All rights reserved.
5 | //
6 | // Redistribution and use in source and binary forms, with or without
7 | // modification, are permitted provided that the following conditions
8 | // are met:
9 | //
10 | // 1. Redistributions of source code must retain the above
11 | // copyright notice, this list of conditions and the following
12 | // disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above
15 | // copyright notice, this list of conditions and the following
16 | // disclaimer in the documentation and/or other materials
17 | // provided with the distribution.
18 | //
19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 | // POSSIBILITY OF SUCH DAMAGE.
31 |
32 | // TESTED ON
33 | // Internet Explorer 6
34 | // Opera 10.01
35 | // Chromium 4.0.237.0 (Ubuntu build 31094)
36 | // Firefox 3.5.8, 3.6.2 (Mac)
37 | // Safari 4.0.5 (6531.22.7) (Mac)
38 | // Google Chrome 5.0.375.55 (Mac)
39 |
40 | (function($){
41 | var isWebkit = !!~navigator.userAgent.indexOf(' AppleWebKit/');
42 |
43 | $.fn.console = function(config){
44 | ////////////////////////////////////////////////////////////////////////
45 | // Constants
46 | // Some are enums, data types, others just for optimization
47 | var keyCodes = {
48 | // left
49 | 37: moveBackward,
50 | // right
51 | 39: moveForward,
52 | // up
53 | 38: previousHistory,
54 | // down
55 | 40: nextHistory,
56 | // backspace
57 | 8: backDelete,
58 | // delete
59 | 46: forwardDelete,
60 | // end
61 | 35: moveToEnd,
62 | // start
63 | 36: moveToStart,
64 | // return
65 | 13: commandTrigger,
66 | // tab
67 | 18: doNothing,
68 | // tab
69 | 9: doComplete,
70 | // escape
71 | 27: deleteFromStart,
72 | };
73 | var ctrlCodes = {
74 | // C-a
75 | 65: moveToStart,
76 | // C-e
77 | 69: moveToEnd,
78 | // C-d
79 | 68: forwardDelete,
80 | // C-n
81 | 78: nextHistory,
82 | // C-p
83 | 80: previousHistory,
84 | // C-b
85 | 66: moveBackward,
86 | // C-f
87 | 70: moveForward,
88 | // C-k
89 | 75: deleteUntilEnd
90 | };
91 | var altCodes = {
92 | // M-f
93 | 70: moveToNextWord,
94 | // M-b
95 | 66: moveToPreviousWord,
96 | // M-d
97 | 68: deleteNextWord
98 | };
99 | var cursor = ' ';
100 |
101 | ////////////////////////////////////////////////////////////////////////
102 | // Globals
103 | var container = $(this);
104 | var inner = $('
');
105 | // erjiang: changed this from a text input to a textarea so we
106 | // can get pasted newlines
107 | var typer = $('');
108 | // Prompt
109 | var promptBox;
110 | var prompt;
111 | var promptLabel = config && config.promptLabel? config.promptLabel : "> ";
112 | var continuedPromptLabel = config && config.continuedPromptLabel?
113 | config.continuedPromptLabel : "> ";
114 | var column = 0;
115 | var promptText = '';
116 | var restoreText = '';
117 | var continuedText = '';
118 | // Prompt history stack
119 | var history = [];
120 | var ringn = 0;
121 | // For reasons unknown to The Sword of Michael himself, Opera
122 | // triggers and sends a key character when you hit various
123 | // keys like PgUp, End, etc. So there is no way of knowing
124 | // when a user has typed '#' or End. My solution is in the
125 | // typer.keydown and typer.keypress functions; I use the
126 | // variable below to ignore the keypress event if the keydown
127 | // event succeeds.
128 | var cancelKeyPress = 0;
129 | // When this value is false, the prompt will not respond to input
130 | var acceptInput = true;
131 | // When this value is true, the command has been canceled
132 | var cancelCommand = false;
133 |
134 | // External exports object
135 | var extern = {};
136 |
137 | ////////////////////////////////////////////////////////////////////////
138 | // Main entry point
139 | (function(){
140 | container.append(inner);
141 | inner.append(typer);
142 | typer.css({position:'absolute',top:0,left:'-9999px'});
143 | if (config.welcomeMessage)
144 | message(config.welcomeMessage,'jquery-console-welcome');
145 | newPromptBox();
146 | if (config.autofocus) {
147 | inner.addClass('jquery-console-focus');
148 | typer.focus();
149 | setTimeout(function(){
150 | inner.addClass('jquery-console-focus');
151 | typer.focus();
152 | },100);
153 | }
154 | extern.inner = inner;
155 | extern.typer = typer;
156 | extern.scrollToBottom = scrollToBottom;
157 | })();
158 |
159 | ////////////////////////////////////////////////////////////////////////
160 | // Reset terminal
161 | extern.reset = function(){
162 | var welcome = (typeof config.welcomeMessage != 'undefined');
163 | inner.find('div').remove();
164 | newPromptBox();
165 | inner.addClass('jquery-console-focus');
166 | typer.focus();
167 | };
168 |
169 | ////////////////////////////////////////////////////////////////////////
170 | // Reset terminal
171 | extern.notice = function(msg,style){
172 | var n = $('
').append($('
').text(msg))
173 | .css({visibility:'hidden'});
174 | container.append(n);
175 | var focused = true;
176 | if (style=='fadeout')
177 | setTimeout(function(){
178 | n.fadeOut(function(){
179 | n.remove();
180 | });
181 | },4000);
182 | else if (style=='prompt') {
183 | var a = $(' ');
184 | n.append(a);
185 | focused = false;
186 | a.click(function(){ n.fadeOut(function(){ n.remove();inner.css({opacity:1}) }); });
187 | }
188 | var h = n.height();
189 | n.css({height:'0px',visibility:'visible'})
190 | .animate({height:h+'px'},function(){
191 | if (!focused) inner.css({opacity:0.5});
192 | });
193 | n.css('cursor','default');
194 | return n;
195 | };
196 |
197 | ////////////////////////////////////////////////////////////////////////
198 | // Reset history
199 | extern.resetHistory = function () {
200 | ringn = 0;
201 | };
202 |
203 | ////////////////////////////////////////////////////////////////////////
204 | // Compute a promptLabel
205 | function getPromptLabel() {
206 | var promptLabelText;
207 | if (typeof promptLabel == 'function') {
208 | promptLabelText = promptLabel();
209 | } else {
210 | promptLabelText = promptLabel;
211 | }
212 | return extern.continuedPrompt ? continuedPromptLabel : promptLabelText;
213 | }
214 |
215 | ////////////////////////////////////////////////////////////////////////
216 | // Make a new prompt box
217 | function newPromptBox() {
218 | column = 0;
219 | promptText = '';
220 | ringn = 0; // Reset the position of the history ring
221 | enableInput();
222 | promptBox = $('
');
223 | var label = $(' ');
224 | var pid = $(' ');
225 | var labelText = getPromptLabel();
226 | promptBox.append(pid.text("").show());
227 | promptBox.append(label.text(labelText).show());
228 | label.html(label.html().replace(' ',' '));
229 | prompt = $(' ');
230 | promptBox.append(prompt);
231 | inner.append(promptBox);
232 | updatePromptDisplay();
233 | };
234 |
235 | ////////////////////////////////////////////////////////////////////////
236 | // Handle setting focus
237 | container.click(function(){
238 | // Don't mess with the focus if there is an active selection
239 | if (window.getSelection().toString()) {
240 | return false;
241 | }
242 |
243 | inner.addClass('jquery-console-focus');
244 | inner.removeClass('jquery-console-nofocus');
245 | if (isWebkit) {
246 | typer.focusWithoutScrolling();
247 | } else {
248 | typer.css('position', 'fixed').focus();
249 | }
250 | scrollToBottom();
251 | return false;
252 | });
253 |
254 | ////////////////////////////////////////////////////////////////////////
255 | // Handle losing focus
256 | typer.blur(function(){
257 | inner.removeClass('jquery-console-focus');
258 | inner.addClass('jquery-console-nofocus');
259 | });
260 |
261 | ////////////////////////////////////////////////////////////////////////
262 | // Bind to the paste event of the input box so we know when we
263 | // get pasted data
264 | typer.bind('paste', function(e) {
265 | // wipe typer input clean just in case
266 | typer.val("");
267 | // this timeout is required because the onpaste event is
268 | // fired *before* the text is actually pasted
269 | setTimeout(function() {
270 | typer.consoleInsert(typer.val());
271 | typer.val("");
272 | }, 0);
273 | });
274 |
275 | ////////////////////////////////////////////////////////////////////////
276 | // Handle key hit before translation
277 | // For picking up control characters like up/left/down/right
278 |
279 | typer.keydown(function(e){
280 | cancelKeyPress = 0;
281 | var keyCode = e.keyCode;
282 | // C-c: cancel the execution
283 | if(e.ctrlKey && keyCode == 67) {
284 | cancelKeyPress = keyCode;
285 | cancelExecution();
286 | return false;
287 | }
288 | if (acceptInput) {
289 | if (typeof config.userInputHandle == 'function') {
290 | config.userInputHandle(keyCode);
291 | }
292 | if (keyCode in keyCodes) {
293 | cancelKeyPress = keyCode;
294 | (keyCodes[keyCode])(e);
295 | return false;
296 | } else if (e.ctrlKey && keyCode in ctrlCodes) {
297 | cancelKeyPress = keyCode;
298 | (ctrlCodes[keyCode])();
299 | return false;
300 | } else if (e.altKey && keyCode in altCodes) {
301 | cancelKeyPress = keyCode;
302 | (altCodes[keyCode])();
303 | return false;
304 | }
305 | }
306 | });
307 |
308 | ////////////////////////////////////////////////////////////////////////
309 | // Handle key press
310 | typer.keypress(function(e){
311 | var keyCode = e.keyCode || e.which;
312 | if (isIgnorableKey(e)) {
313 | return false;
314 | }
315 | // C-v: don't insert on paste event
316 | if ((e.ctrlKey || e.metaKey) && String.fromCharCode(keyCode).toLowerCase() == 'v') {
317 | return true;
318 | }
319 | if (acceptInput && cancelKeyPress != keyCode && keyCode >= 32){
320 | if (cancelKeyPress) return false;
321 | if (
322 | typeof config.charInsertTrigger == 'undefined' || (
323 | typeof config.charInsertTrigger == 'function' &&
324 | config.charInsertTrigger(keyCode,promptText)
325 | )
326 | ){
327 | typer.consoleInsert(keyCode);
328 | }
329 | }
330 | if (isWebkit) return false;
331 | });
332 |
333 | function isIgnorableKey(e) {
334 | // for now just filter alt+tab that we receive on some platforms when
335 | // user switches windows (goes away from the browser)
336 | return ((e.keyCode == keyCodes.tab || e.keyCode == 192) && e.altKey);
337 | };
338 |
339 | ////////////////////////////////////////////////////////////////////////
340 | // Rotate through the command history
341 | function rotateHistory(n){
342 | if (history.length == 0) return;
343 | ringn += n;
344 | if (ringn < 0) ringn = history.length;
345 | else if (ringn > history.length) ringn = 0;
346 | var prevText = promptText;
347 | if (ringn == 0) {
348 | promptText = restoreText;
349 | } else {
350 | promptText = history[ringn - 1];
351 | }
352 | if (config.historyPreserveColumn) {
353 | if (promptText.length < column + 1) {
354 | column = promptText.length;
355 | } else if (column == 0) {
356 | column = promptText.length;
357 | }
358 | } else {
359 | column = promptText.length;
360 | }
361 | updatePromptDisplay();
362 | };
363 |
364 | function previousHistory() {
365 | rotateHistory(-1);
366 | };
367 |
368 | function nextHistory() {
369 | rotateHistory(1);
370 | };
371 |
372 | // Add something to the history ring
373 | function addToHistory(line) {
374 | if (!line || line.trim() == "")
375 | return;
376 | history.push(line);
377 | restoreText = '';
378 | };
379 |
380 | // Delete the character at the current position
381 | function deleteCharAtPos(){
382 | if (column < promptText.length){
383 | promptText =
384 | promptText.substring(0,column) +
385 | promptText.substring(column+1);
386 | restoreText = promptText;
387 | return true;
388 | } else return false;
389 | };
390 |
391 | function backDelete() {
392 | if (moveColumn(-1)){
393 | deleteCharAtPos();
394 | updatePromptDisplay();
395 | }
396 | };
397 |
398 | function forwardDelete() {
399 | if (deleteCharAtPos()){
400 | updatePromptDisplay();
401 | }
402 | };
403 |
404 | function deleteUntilEnd() {
405 | while(deleteCharAtPos()) {
406 | updatePromptDisplay();
407 | }
408 | };
409 |
410 | function deleteFromStart() {
411 | if (moveColumn(-column)) {
412 | while (deleteCharAtPos());
413 | updatePromptDisplay();
414 | }
415 | }
416 |
417 | function deleteNextWord() {
418 | // A word is defined within this context as a series of alphanumeric
419 | // characters.
420 | // Delete up to the next alphanumeric character
421 | while(
422 | column < promptText.length &&
423 | !isCharAlphanumeric(promptText[column])
424 | ) {
425 | deleteCharAtPos();
426 | updatePromptDisplay();
427 | }
428 | // Then, delete until the next non-alphanumeric character
429 | while(
430 | column < promptText.length &&
431 | isCharAlphanumeric(promptText[column])
432 | ) {
433 | deleteCharAtPos();
434 | updatePromptDisplay();
435 | }
436 | };
437 |
438 | ////////////////////////////////////////////////////////////////////////
439 | // Validate command and trigger it if valid, or show a validation error
440 | function commandTrigger() {
441 | var line = promptText;
442 | if (typeof config.commandValidate == 'function') {
443 | var ret = config.commandValidate(line);
444 | if (ret == true || ret == false) {
445 | if (ret) {
446 | handleCommand();
447 | }
448 | } else {
449 | commandResult(ret,"jquery-console-message-error");
450 | }
451 | } else {
452 | handleCommand();
453 | }
454 | };
455 |
456 | // Scroll to the bottom of the view
457 | function scrollToBottom() {
458 | if (jQuery.fn.jquery > "1.6") {
459 | inner.prop({ scrollTop: inner.prop("scrollHeight") });
460 | }
461 | else {
462 | inner.attr({ scrollTop: inner.attr("scrollHeight") });
463 | }
464 | };
465 |
466 | function cancelExecution() {
467 | if(typeof config.cancelHandle == 'function') {
468 | config.cancelHandle();
469 | }
470 | }
471 |
472 | ////////////////////////////////////////////////////////////////////////
473 | // Handle a command
474 | function handleCommand() {
475 | if (typeof config.commandHandle == 'function') {
476 | disableInput();
477 | addToHistory(promptText);
478 | var text = promptText;
479 | if (extern.continuedPrompt) {
480 | if (continuedText)
481 | continuedText += '\n' + promptText;
482 | else continuedText = promptText;
483 | } else continuedText = undefined;
484 | if (continuedText) text = continuedText;
485 | var ret = config.commandHandle(text,function(msgs){
486 | commandResult(msgs);
487 | });
488 | if (extern.continuedPrompt && !continuedText)
489 | continuedText = promptText;
490 | if (typeof ret == 'boolean') {
491 | if (ret) {
492 | // Command succeeded without a result.
493 | commandResult();
494 | } else {
495 | commandResult(
496 | 'Command failed.',
497 | "jquery-console-message-error"
498 | );
499 | }
500 | } else if (typeof ret == "string") {
501 | commandResult(ret,"jquery-console-message-success");
502 | } else if (typeof ret == 'object' && ret.length) {
503 | commandResult(ret);
504 | } else if (extern.continuedPrompt) {
505 | commandResult();
506 | }
507 | }
508 | };
509 |
510 | ////////////////////////////////////////////////////////////////////////
511 | // Disable input
512 | function disableInput() {
513 | acceptInput = false;
514 | };
515 |
516 | // Enable input
517 | function enableInput() {
518 | acceptInput = true;
519 | }
520 |
521 | ////////////////////////////////////////////////////////////////////////
522 | // Reset the prompt in invalid command
523 | function commandResult(msg,className,ignorePrompt) {
524 | column = -1;
525 | if (!ignorePrompt) {
526 | updatePromptDisplay();
527 | }
528 | if (typeof msg == 'string') {
529 | message(msg,className);
530 | } else if ($.isArray(msg)) {
531 | for (var x in msg) {
532 | var ret = msg[x];
533 | message(ret.msg,ret.className);
534 | }
535 | } else { // Assume it's a DOM node or jQuery object.
536 | inner.append(msg);
537 | }
538 | if (!ignorePrompt) {
539 | newPromptBox();
540 | }
541 | };
542 |
543 | ////////////////////////////////////////////////////////////////////////
544 | // Display a message
545 | function message(msg,className) {
546 | var mesg = $('
');
547 | if (className) mesg.addClass(className);
548 | mesg.filledText(msg).hide();
549 | inner.append(mesg);
550 | mesg.show();
551 | return mesg;
552 | };
553 |
554 | extern.message = message;
555 | extern.enableInput = enableInput;
556 |
557 | ////////////////////////////////////////////////////////////////////////
558 | // Handle normal character insertion
559 | // data can either be a number, which will be interpreted as the
560 | // numeric value of a single character, or a string
561 | typer.consoleInsert = function(data){
562 | // TODO: remove redundant indirection
563 | var text = isNaN(data) ? data : String.fromCharCode(data);
564 | var before = promptText.substring(0,column);
565 | var after = promptText.substring(column);
566 | promptText = before + text + after;
567 | moveColumn(text.length);
568 | restoreText = promptText;
569 | updatePromptDisplay();
570 | };
571 |
572 | ////////////////////////////////////////////////////////////////////////
573 | // Move to another column relative to this one
574 | // Negative means go back, positive means go forward.
575 | function moveColumn(n){
576 | if (column + n >= 0 && column + n <= promptText.length){
577 | column += n;
578 | return true;
579 | } else return false;
580 | };
581 |
582 | function moveForward() {
583 | if(moveColumn(1)) {
584 | updatePromptDisplay();
585 | return true;
586 | }
587 | return false;
588 | };
589 |
590 | function moveBackward() {
591 | if(moveColumn(-1)) {
592 | updatePromptDisplay();
593 | return true;
594 | }
595 | return false;
596 | };
597 |
598 | function moveToStart() {
599 | if (moveColumn(-column))
600 | updatePromptDisplay();
601 | };
602 |
603 | function moveToEnd() {
604 | if (moveColumn(promptText.length-column))
605 | updatePromptDisplay();
606 | };
607 |
608 | function moveToNextWord() {
609 | while(
610 | column < promptText.length &&
611 | !isCharAlphanumeric(promptText[column]) &&
612 | moveForward()
613 | ) {}
614 | while(
615 | column < promptText.length &&
616 | isCharAlphanumeric(promptText[column]) &&
617 | moveForward()
618 | ) {}
619 | };
620 |
621 | function moveToPreviousWord() {
622 | // Move backward until we find the first alphanumeric
623 | while(
624 | column -1 >= 0 &&
625 | !isCharAlphanumeric(promptText[column-1]) &&
626 | moveBackward()
627 | ) {}
628 | // Move until we find the first non-alphanumeric
629 | while(
630 | column -1 >= 0 &&
631 | isCharAlphanumeric(promptText[column-1]) &&
632 | moveBackward()
633 | ) {}
634 | };
635 |
636 | function isCharAlphanumeric(charToTest) {
637 | if(typeof charToTest == 'string') {
638 | var code = charToTest.charCodeAt();
639 | return (code >= 'A'.charCodeAt() && code <= 'Z'.charCodeAt()) ||
640 | (code >= 'a'.charCodeAt() && code <= 'z'.charCodeAt()) ||
641 | (code >= '0'.charCodeAt() && code <= '9'.charCodeAt());
642 | }
643 | return false;
644 | };
645 |
646 | function doComplete(e) {
647 | if(typeof config.completeHandle == 'function') {
648 | var completions = config.completeHandle(promptText, e.shiftKey);
649 | if (!completions) {
650 | return;
651 | }
652 | var len = completions.length;
653 | if (len === 1) {
654 | extern.promptText(completions[0]);
655 | } else if (len > 1 && config.cols) {
656 | var prompt = promptText;
657 | // Compute the number of rows that will fit in the width
658 | var max = 0;
659 | for (var i = 0;i < len;i++) {
660 | max = Math.max(max, completions[i].length);
661 | }
662 | max += 2;
663 | var n = Math.floor(config.cols / max);
664 | var buffer = "";
665 | var col = 0;
666 | for (i = 0;i < len;i++) {
667 | var completion = completions[i];
668 | buffer += completions[i];
669 | for (var j = completion.length;j < max;j++) {
670 | buffer += " ";
671 | }
672 | if (++col >= n) {
673 | buffer += "\n";
674 | col = 0;
675 | }
676 | }
677 | commandResult(buffer,"jquery-console-message-value");
678 | extern.promptText(prompt);
679 | }
680 | }
681 | };
682 |
683 | function doNothing() {};
684 |
685 | extern.promptText = function(text){
686 | if (typeof text === 'string') {
687 | promptText = text;
688 | column = promptText.length;
689 | updatePromptDisplay();
690 | }
691 | return promptText;
692 | };
693 |
694 | ////////////////////////////////////////////////////////////////////////
695 | // Update the prompt display
696 | function updatePromptDisplay(){
697 | var line = promptText;
698 | var html = '';
699 | if (column > 0 && line == ''){
700 | // When we have an empty line just display a cursor.
701 | html = cursor;
702 | } else if (column == promptText.length){
703 | // We're at the end of the line, so we need to display
704 | // the text *and* cursor.
705 | html = htmlEncode(line) + cursor;
706 | } else {
707 | // Grab the current character, if there is one, and
708 | // make it the current cursor.
709 | var before = line.substring(0, column);
710 | var current = line.substring(column,column+1);
711 | if (current){
712 | current =
713 | '' +
714 | htmlEncode(current) +
715 | ' ';
716 | }
717 | var after = line.substring(column+1);
718 | html = htmlEncode(before) + current + htmlEncode(after);
719 | }
720 | prompt.html(html);
721 | scrollToBottom();
722 | };
723 |
724 | // Simple HTML encoding
725 | // Simply replace '<', '>' and '&'
726 | // TODO: Use jQuery's .html() trick, or grab a proper, fast
727 | // HTML encoder.
728 | function htmlEncode(text){
729 | return (
730 | text.replace(/&/g,'&')
731 | .replace(/');
12 | var curReportFun;
13 | var height = parseInt(window.localStorage.debugconsole_height);
14 | height = !!height ? height : 500;
15 | var heightOffset = height / 10;
16 | var controller = webtermConsole.console({
17 | continuedPrompt: true,
18 | promptLabel: function () {
19 | return getJSONValue(lastLine);
20 | },
21 | commandValidate: function () {
22 | return true;
23 | },
24 | commandHandle: function (line, reportFn) {
25 | curReportFun = reportFn;
26 | if (line.trim().toUpperCase() === "CLS") {
27 | controller.reset();
28 | $(".jquery-console-inner").append($(".jquery-console-prompt-box").css("display", "inline-block"));
29 | controller.message("", "jquery-console-message-value");
30 | } else {
31 | lastUserInput = line + "\n";
32 | if (lastLine.output) {
33 | lastLine.output += lastUserInput;
34 | } else if (lastLine.error) {
35 | lastLine.error += lastUserInput;
36 | } else {
37 | lastLine.output = lastUserInput;
38 | }
39 | _sendCommand(lastUserInput);
40 | controller.resetHistory();
41 | DisplayAndUpdate(lastLine);
42 | lastLine = {
43 | output: "",
44 | error: ""
45 | };
46 | DisplayAndUpdate(lastLine);
47 | }
48 | },
49 | cancelHandle: function () {
50 | //sending CTRL+C character (^C) to the server to cancel the current command
51 | _sendCommand("\x03");
52 | },
53 | completeHandle: function (line, reverse) {
54 | return "";
55 | },
56 | userInputHandle: function (keycode) {
57 | //reset the string we match on if the user type anything other than tab == 9
58 | if (keycode !== 9 && keycode != 16) {
59 | originalMatchString = undefined;
60 | }
61 | },
62 | cols: 3,
63 | autofocus: true,
64 | animateScroll: true,
65 | promptHistory: true,
66 | welcomeMessage: "Web Terminal\r\nType 'cls' to clear the console\r\n\r\n"
67 | });
68 | window.$webtermConsole = $('#webtermConsole');
69 | window.$webtermConsole.append(webtermConsole);
70 |
71 | var connection = new WebSocketManager.Connection("ws://localhost:5000/cmd");
72 | window.$webtermConsole.data('connection', connection);
73 |
74 | connection.connectionMethods.onConnected = () => {
75 | console.log("You are now connected! Connection ID: " + connection.connectionId);
76 | };
77 |
78 | connection.connectionMethods.onDisconnected = () => {
79 | console.log("Disconnected!");
80 | }
81 |
82 | connection.clientMethods["receiveMessage"] = (message) => {
83 | console.log("receiveMessage: " + message);
84 | DisplayAndUpdate(message);
85 | controller.enableInput();
86 | };
87 |
88 | connection.start();
89 |
90 | function _sendCommand(input) {
91 | console.log("_sendCommand: " + input);
92 | connection.invoke("ReceiveMessage", connection.connectionId, input);
93 | }
94 |
95 | function endsWith(str, suffix) {
96 | return str.indexOf(suffix, str.length - suffix.length) !== -1;
97 | }
98 |
99 | function startsWith(str, prefix) {
100 | return str.indexOf(prefix) == 0;
101 | }
102 |
103 | function getJSONValue(input) {
104 | return input ? (input.output || input.error || "").toString() : "";
105 | }
106 |
107 | function DisplayAndUpdate(data) {
108 | var prompt = getJSONValue(data);
109 | var lastLinestr = getJSONValue(lastLine);
110 | //this means the last command should be cleared and the next one will be written over it.
111 | // case 1. lastLine = "progress 10%", prompt = "\r" ==> lastLine is not written into HTML (curl)
112 | // case 2. lastLine = "progress 10%\r", prompt = "progress 20%\r" ==> lastLine is not written into HTML (youtube-dl)
113 | // lastLine = "version 123\r", prompt = "\r\n" ==> lastLine IS WRITTEN into HTML (dotnet tsc)
114 | if ((endsWith(prompt, "\r") && !endsWith(lastLinestr, "\n")) ||
115 | (endsWith(lastLinestr, "\r") && prompt !== "\r\n" && prompt !== "\n")) {
116 | lastLinestr = "";
117 | lastLine = null;
118 | }
119 |
120 | var consoleMessages = $(".jquery-console-message");
121 | if (consoleMessages.length > height && consoleMessages.length % heightOffset == (heightOffset - 1)) {
122 | consoleMessages.slice(0, consoleMessages.length - height).remove();
123 | }
124 |
125 | //if the data has the same class as the last ".jquery-console-message"
126 | //then just append it to the last one, if not, create a new div.
127 | var lastConsoleMessage = consoleMessages.last();
128 | lastConsoleMessage.text(lastConsoleMessage.text() + lastLinestr);
129 | lastLine = null;
130 |
131 | //if the prompt is just \r this means that we don't really need to display anything, just marking the line as
132 | if (prompt == "\r") {
133 | return;
134 | }
135 |
136 | // display output, but not updating the HTML
137 | $(".jquery-console-inner").append($(".jquery-console-prompt-box").last().css("display", "inline"));
138 | if (data.error) {
139 | $(".jquery-console-prompt-label").last().text(prompt).css("color", "red");
140 | } else {
141 | $(".jquery-console-prompt-label").last().text(prompt).css("color", "white");
142 | }
143 |
144 | controller.promptText("");
145 |
146 | //Now create the div for the new line that will be printed the next time with the correct class
147 | if (data.error) {
148 | if (!lastConsoleMessage.hasClass("jquery-console-message-error")) {
149 | controller.message("", "jquery-console-message-error");
150 | }
151 | } else if (!lastConsoleMessage.hasClass("jquery-console-message-value") || endsWith(lastLinestr, "\n")) {
152 | controller.message("", "jquery-console-message-value");
153 | }
154 |
155 | //save last line for next time.
156 | lastLine = data;
157 | prompt = prompt.trim();
158 | }
159 |
160 | window.setInterval(function () {
161 | controller.enableInput();
162 | }, 2000);
163 | }
164 |
165 | $(function () {
166 | LoadConsole();
167 | })
168 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/WebSocketManager.Client.TS.njsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 14.0
4 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
5 | WebSocketManager.Client.TS
6 | WebSocketManager.Client.TS
7 |
8 |
9 |
10 | Debug
11 | 2.0
12 | d62c48e8-ec40-4904-8e59-f33ef4dcde25
13 | .
14 | server.ts
15 | True
16 |
17 |
18 | .
19 | .
20 | v4.0
21 | {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}
22 | 1337
23 | true
24 | CommonJS
25 | true
26 | true
27 |
28 |
29 | true
30 |
31 |
32 | true
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | False
47 | True
48 | 0
49 | /
50 | http://localhost:48022/
51 | False
52 | True
53 | http://localhost:1337
54 | False
55 |
56 |
57 |
58 |
59 |
60 |
61 | CurrentPage
62 | True
63 | False
64 | False
65 | False
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | False
75 | False
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/dist/WebSocketManager.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory();
4 | else if(typeof define === 'function' && define.amd)
5 | define("WebSocketManager", [], factory);
6 | else if(typeof exports === 'object')
7 | exports["WebSocketManager"] = factory();
8 | else
9 | root["WebSocketManager"] = factory();
10 | })(this, function() {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 | /******/
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ exports: {},
25 | /******/ id: moduleId,
26 | /******/ loaded: false
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.loaded = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // __webpack_public_path__
47 | /******/ __webpack_require__.p = "";
48 | /******/
49 | /******/ // Load entry module and return exports
50 | /******/ return __webpack_require__(0);
51 | /******/ })
52 | /************************************************************************/
53 | /******/ ([
54 | /* 0 */
55 | /***/ function(module, exports, __webpack_require__) {
56 |
57 | module.exports = __webpack_require__(1);
58 |
59 |
60 | /***/ },
61 | /* 1 */
62 | /***/ function(module, exports, __webpack_require__) {
63 |
64 | "use strict";
65 | var InvocationDescriptor_1 = __webpack_require__(2);
66 | var Message_1 = __webpack_require__(3);
67 | var Connection = (function () {
68 | function Connection(url, enableLogging) {
69 | var _this = this;
70 | if (enableLogging === void 0) { enableLogging = false; }
71 | this.enableLogging = false;
72 | this.clientMethods = {};
73 | this.connectionMethods = {};
74 | this.url = url;
75 | this.enableLogging = enableLogging;
76 | this.connectionMethods['onConnected'] = function () {
77 | if (_this.enableLogging) {
78 | console.log('Connected! connectionId: ' + _this.connectionId);
79 | }
80 | };
81 | this.connectionMethods['onDisconnected'] = function () {
82 | if (_this.enableLogging) {
83 | console.log('Connection closed from: ' + _this.url);
84 | }
85 | };
86 | this.connectionMethods['onOpen'] = function (socketOpenedEvent) {
87 | if (_this.enableLogging) {
88 | console.log('WebSockets connection opened!');
89 | }
90 | };
91 | }
92 | Connection.prototype.start = function () {
93 | var _this = this;
94 | this.socket = new WebSocket(this.url);
95 | this.socket.onopen = function (event) {
96 | _this.connectionMethods['onOpen'].apply(_this, event);
97 | };
98 | this.socket.onmessage = function (event) {
99 | _this.message = JSON.parse(event.data);
100 | if (_this.message.messageType == Message_1.MessageType.Text) {
101 | if (_this.enableLogging) {
102 | console.log('Text message received. Message: ' + _this.message.data);
103 | }
104 | }
105 | else if (_this.message.messageType == Message_1.MessageType.MethodInvocation) {
106 | var invocationDescriptor = JSON.parse(_this.message.data);
107 | _this.clientMethods[invocationDescriptor.methodName].apply(_this, invocationDescriptor.arguments);
108 | }
109 | else if (_this.message.messageType == Message_1.MessageType.ConnectionEvent) {
110 | _this.connectionId = _this.message.data;
111 | _this.connectionMethods['onConnected'].apply(_this);
112 | }
113 | };
114 | this.socket.onclose = function (event) {
115 | _this.connectionMethods['onDisconnected'].apply(_this);
116 | };
117 | this.socket.onerror = function (event) {
118 | if (_this.enableLogging) {
119 | console.log('Error data: ' + event.error);
120 | }
121 | };
122 | };
123 | Connection.prototype.invoke = function (methodName) {
124 | var args = [];
125 | for (var _i = 1; _i < arguments.length; _i++) {
126 | args[_i - 1] = arguments[_i];
127 | }
128 | var invocationDescriptor = new InvocationDescriptor_1.InvocationDescriptor(methodName, args);
129 | if (this.enableLogging) {
130 | console.log(invocationDescriptor);
131 | }
132 | this.socket.send(JSON.stringify(invocationDescriptor));
133 | };
134 | return Connection;
135 | }());
136 | exports.Connection = Connection;
137 |
138 |
139 | /***/ },
140 | /* 2 */
141 | /***/ function(module, exports) {
142 |
143 | "use strict";
144 | var InvocationDescriptor = (function () {
145 | function InvocationDescriptor(methodName, args) {
146 | this.methodName = methodName;
147 | this.arguments = args;
148 | }
149 | return InvocationDescriptor;
150 | }());
151 | exports.InvocationDescriptor = InvocationDescriptor;
152 |
153 |
154 | /***/ },
155 | /* 3 */
156 | /***/ function(module, exports) {
157 |
158 | "use strict";
159 | (function (MessageType) {
160 | MessageType[MessageType["Text"] = 0] = "Text";
161 | MessageType[MessageType["MethodInvocation"] = 1] = "MethodInvocation";
162 | MessageType[MessageType["ConnectionEvent"] = 2] = "ConnectionEvent";
163 | })(exports.MessageType || (exports.MessageType = {}));
164 | var MessageType = exports.MessageType;
165 | var Message = (function () {
166 | function Message() {
167 | }
168 | return Message;
169 | }());
170 | exports.Message = Message;
171 |
172 |
173 | /***/ }
174 | /******/ ])
175 | });
176 | ;
177 | //# sourceMappingURL=WebSocketManager.js.map
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "websocket-manager-typescript-client",
3 | "version": "0.1.0",
4 | "description": "",
5 | "main": "dist/WebSocketManager.min.js",
6 | "scripts": {
7 | "prepublish": "webpack --debug; webpack -p"
8 | },
9 | "author": "Radu Matei ",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "mocha": "^2.4.5",
13 | "ts-loader": "^0.8.1",
14 | "tslint": "^3.5.0",
15 | "tslint-loader": "^2.1.0",
16 | "typescript": "^1.8.2",
17 | "webpack": "^1.12.14",
18 | "yargs": "^4.2.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/Connection.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var InvocationDescriptor_1 = require("./InvocationDescriptor");
3 | var Message_1 = require("./Message");
4 | var Connection = (function () {
5 | function Connection(url, enableLogging) {
6 | if (enableLogging === void 0) { enableLogging = false; }
7 | var _this = this;
8 | this.enableLogging = false;
9 | this.clientMethods = {};
10 | this.connectionMethods = {};
11 | this.url = url;
12 | this.enableLogging = enableLogging;
13 | this.connectionMethods['onConnected'] = function () {
14 | if (_this.enableLogging) {
15 | console.log('Connected! connectionId: ' + _this.connectionId);
16 | }
17 | };
18 | this.connectionMethods['onDisconnected'] = function () {
19 | if (_this.enableLogging) {
20 | console.log('Connection closed from: ' + _this.url);
21 | }
22 | };
23 | this.connectionMethods['onOpen'] = function (socketOpenedEvent) {
24 | if (_this.enableLogging) {
25 | console.log('WebSockets connection opened!');
26 | }
27 | };
28 | }
29 | Connection.prototype.start = function () {
30 | var _this = this;
31 | this.socket = new WebSocket(this.url);
32 | this.socket.onopen = function (event) {
33 | _this.connectionMethods['onOpen'].apply(_this, event);
34 | };
35 | this.socket.onmessage = function (event) {
36 | _this.message = JSON.parse(event.data);
37 | if (_this.message.messageType == Message_1.MessageType.Text) {
38 | if (_this.enableLogging) {
39 | console.log('Text message received. Message: ' + _this.message.data);
40 | }
41 | }
42 | else if (_this.message.messageType == Message_1.MessageType.MethodInvocation) {
43 | var invocationDescriptor = JSON.parse(_this.message.data);
44 | _this.clientMethods[invocationDescriptor.methodName].apply(_this, invocationDescriptor.arguments);
45 | }
46 | else if (_this.message.messageType == Message_1.MessageType.ConnectionEvent) {
47 | _this.connectionId = _this.message.data;
48 | _this.connectionMethods['onConnected'].apply(_this);
49 | }
50 | };
51 | this.socket.onclose = function (event) {
52 | _this.connectionMethods['onDisconnected'].apply(_this);
53 | };
54 | this.socket.onerror = function (event) {
55 | if (_this.enableLogging) {
56 | console.log('Error data: ' + event.error);
57 | }
58 | };
59 | };
60 | Connection.prototype.invoke = function (methodName) {
61 | var args = [];
62 | for (var _i = 1; _i < arguments.length; _i++) {
63 | args[_i - 1] = arguments[_i];
64 | }
65 | var invocationDescriptor = new InvocationDescriptor_1.InvocationDescriptor(methodName, args);
66 | if (this.enableLogging) {
67 | console.log(invocationDescriptor);
68 | }
69 | this.socket.send(JSON.stringify(invocationDescriptor));
70 | };
71 | return Connection;
72 | }());
73 | exports.Connection = Connection;
74 | //# sourceMappingURL=Connection.js.map
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/Connection.ts:
--------------------------------------------------------------------------------
1 | import { InvocationDescriptor } from './InvocationDescriptor'
2 | import { Message, MessageType } from './Message'
3 |
4 | export class Connection {
5 |
6 | public url: string;
7 | public connectionId: string;
8 | public enableLogging: boolean = false;
9 |
10 | protected message: Message;
11 | protected socket: WebSocket;
12 |
13 | public clientMethods: { [s: string]: Function; } = {};
14 | public connectionMethods: { [s: string]: Function; } = {};
15 |
16 | constructor(url: string, enableLogging: boolean=false) {
17 | this.url = url;
18 |
19 | this.enableLogging = enableLogging;
20 |
21 | this.connectionMethods['onConnected'] = () => {
22 | if(this.enableLogging) {
23 | console.log('Connected! connectionId: ' + this.connectionId);
24 | }
25 | }
26 |
27 | this.connectionMethods['onDisconnected'] = () => {
28 | if(this.enableLogging) {
29 | console.log('Connection closed from: ' + this.url);
30 | }
31 | }
32 |
33 | this.connectionMethods['onOpen'] = (socketOpenedEvent: any) => {
34 | if(this.enableLogging) {
35 | console.log('WebSockets connection opened!');
36 | }
37 | }
38 | }
39 |
40 | public start() {
41 | this.socket = new WebSocket(this.url);
42 |
43 | this.socket.onopen = (event: MessageEvent) => {
44 | this.connectionMethods['onOpen'].apply(this, event);
45 | };
46 |
47 | this.socket.onmessage = (event: MessageEvent) => {
48 | this.message = JSON.parse(event.data);
49 |
50 | if (this.message.messageType == MessageType.Text) {
51 | if(this.enableLogging) {
52 | console.log('Text message received. Message: ' + this.message.data);
53 | }
54 | }
55 |
56 | else if (this.message.messageType == MessageType.MethodInvocation) {
57 | let invocationDescriptor: InvocationDescriptor = JSON.parse(this.message.data);
58 |
59 | this.clientMethods[invocationDescriptor.methodName].apply(this, invocationDescriptor.arguments);
60 | }
61 |
62 | else if (this.message.messageType == MessageType.ConnectionEvent) {
63 | this.connectionId = this.message.data;
64 | this.connectionMethods['onConnected'].apply(this);
65 | }
66 | }
67 |
68 | this.socket.onclose = (event: CloseEvent) => {
69 | this.connectionMethods['onDisconnected'].apply(this);
70 | }
71 |
72 | this.socket.onerror = (event: ErrorEvent) => {
73 | if(this.enableLogging) {
74 | console.log('Error data: ' + event.error);
75 | }
76 | }
77 | }
78 |
79 | public invoke(methodName: string, ...args: any[]) {
80 | let invocationDescriptor = new InvocationDescriptor(methodName, args);
81 |
82 | if(this.enableLogging) {
83 | console.log(invocationDescriptor);
84 | }
85 |
86 | this.socket.send(JSON.stringify(invocationDescriptor));
87 | }
88 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/InvocationDescriptor.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var InvocationDescriptor = (function () {
3 | function InvocationDescriptor(methodName, args) {
4 | this.methodName = methodName;
5 | this.arguments = args;
6 | }
7 | return InvocationDescriptor;
8 | }());
9 | exports.InvocationDescriptor = InvocationDescriptor;
10 | //# sourceMappingURL=InvocationDescriptor.js.map
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/InvocationDescriptor.ts:
--------------------------------------------------------------------------------
1 | export class InvocationDescriptor {
2 | public methodName: string;
3 | public arguments: Array;
4 |
5 | constructor(methodName: string, args: any[]) {
6 | this.methodName = methodName;
7 | this.arguments = args;
8 | }
9 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/Message.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var MessageType;
3 | (function (MessageType) {
4 | MessageType[MessageType["Text"] = 0] = "Text";
5 | MessageType[MessageType["MethodInvocation"] = 1] = "MethodInvocation";
6 | MessageType[MessageType["ConnectionEvent"] = 2] = "ConnectionEvent";
7 | })(MessageType = exports.MessageType || (exports.MessageType = {}));
8 | var Message = (function () {
9 | function Message() {
10 | }
11 | return Message;
12 | }());
13 | exports.Message = Message;
14 | //# sourceMappingURL=Message.js.map
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/src/Message.ts:
--------------------------------------------------------------------------------
1 | export enum MessageType {
2 | Text = 0,
3 | MethodInvocation = 1,
4 | ConnectionEvent = 2
5 | }
6 |
7 | export class Message {
8 | public messageType: MessageType;
9 | public data: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "jsx": "react",
6 | "module": "commonjs",
7 | "noImplicitAny": true,
8 | // "preserveConstEnums": true,
9 | "removeComments": false,
10 | "sourceMap": true,
11 | "target": "es5"
12 | },
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "quotemark": [ true, "single" ]
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client.TS/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack'),
2 | path = require('path'),
3 | yargs = require('yargs');
4 |
5 | var libraryName = 'WebSocketManager',
6 | plugins = [],
7 | outputFile;
8 |
9 | if (yargs.argv.p) {
10 | plugins.push(new webpack.optimize.UglifyJsPlugin({ minimize: true }));
11 | outputFile = libraryName + '.min.js';
12 | } else {
13 | outputFile = libraryName + '.js';
14 | }
15 |
16 | var config = {
17 | entry: [
18 | __dirname + '/src/Connection.ts'
19 | ],
20 | devtool: 'source-map',
21 | output: {
22 | path: path.join(__dirname, '/dist'),
23 | filename: outputFile,
24 | library: libraryName,
25 | libraryTarget: 'umd',
26 | umdNamedDefine: true
27 | },
28 | module: {
29 | preLoaders: [
30 | { test: /\.tsx?$/, loader: 'tslint', exclude: /node_modules/ }
31 | ],
32 | loaders: [
33 | { test: /\.tsx?$/, loader: 'ts', exclude: /node_modules/ }
34 | ]
35 | },
36 | resolve: {
37 | root: path.resolve('./src'),
38 | extensions: [ '', '.js', '.ts', '.jsx', '.tsx' ]
39 | },
40 | plugins: plugins,
41 |
42 | // Individual Plugin Options
43 | tslint: {
44 | emitErrors: true,
45 | failOnHint: true
46 | }
47 | };
48 |
49 | module.exports = config;
50 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Client/Connection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net.WebSockets;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Newtonsoft.Json;
9 | using Newtonsoft.Json.Serialization;
10 |
11 | using WebSocketManager.Common;
12 |
13 | namespace WebSocketManager.Client
14 | {
15 | public class Connection
16 | {
17 | public string ConnectionId { get; set; }
18 |
19 | private ClientWebSocket _clientWebSocket { get; set; }
20 |
21 | private JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings()
22 | {
23 | ContractResolver = new CamelCasePropertyNamesContractResolver(),
24 | TypeNameHandling = TypeNameHandling.All,
25 | TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
26 | SerializationBinder = new JsonBinderWithoutAssembly()
27 | };
28 |
29 | ///
30 | /// Gets the method invocation strategy.
31 | ///
32 | /// The method invocation strategy.
33 | public MethodInvocationStrategy MethodInvocationStrategy { get; }
34 |
35 | ///
36 | /// The waiting remote invocations for Client to Server method calls.
37 | ///
38 | private Dictionary> _waitingRemoteInvocations = new Dictionary>();
39 |
40 | ///
41 | /// Initializes a new instance of the class.
42 | ///
43 | /// The method invocation strategy used for incoming requests.
44 | public Connection(MethodInvocationStrategy methodInvocationStrategy)
45 | {
46 | MethodInvocationStrategy = methodInvocationStrategy;
47 | _jsonSerializerSettings.Converters.Insert(0, new PrimitiveJsonConverter());
48 | }
49 |
50 | public async Task StartConnectionAsync(string uri)
51 | {
52 | // also check if connection was lost, that's probably why we get called multiple times.
53 | if (_clientWebSocket == null || _clientWebSocket.State != WebSocketState.Open)
54 | {
55 | // create a new web-socket so the next connect call works.
56 | _clientWebSocket?.Dispose();
57 | _clientWebSocket = new ClientWebSocket();
58 | }
59 | // don't do anything, we are already connected.
60 | else return;
61 |
62 | await _clientWebSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
63 |
64 | await Receive(_clientWebSocket, async (receivedMessage) =>
65 | {
66 | if (receivedMessage.MessageType == MessageType.ConnectionEvent)
67 | {
68 | this.ConnectionId = receivedMessage.Data;
69 | }
70 | else if (receivedMessage.MessageType == MessageType.MethodInvocation)
71 | {
72 | // retrieve the method invocation request.
73 | InvocationDescriptor invocationDescriptor = null;
74 | try
75 | {
76 | invocationDescriptor = JsonConvert.DeserializeObject(receivedMessage.Data, _jsonSerializerSettings);
77 | if (invocationDescriptor == null) return;
78 | }
79 | catch { return; } // ignore invalid data sent to the client.
80 |
81 | // if the unique identifier hasn't been set then the server doesn't want a return value.
82 | if (invocationDescriptor.Identifier == Guid.Empty)
83 | {
84 | // invoke the method only.
85 | try
86 | {
87 | await MethodInvocationStrategy.OnInvokeMethodReceivedAsync(_clientWebSocket, invocationDescriptor);
88 | }
89 | catch (Exception)
90 | {
91 | // we consume all exceptions.
92 | }
93 | }
94 | else
95 | {
96 | // invoke the method and get the result.
97 | InvocationResult invokeResult;
98 | try
99 | {
100 | // create an invocation result with the results.
101 | invokeResult = new InvocationResult()
102 | {
103 | Identifier = invocationDescriptor.Identifier,
104 | Result = await MethodInvocationStrategy.OnInvokeMethodReceivedAsync(_clientWebSocket, invocationDescriptor),
105 | Exception = null
106 | };
107 | }
108 | // send the exception as the invocation result if there was one.
109 | catch (Exception ex)
110 | {
111 | invokeResult = new InvocationResult()
112 | {
113 | Identifier = invocationDescriptor.Identifier,
114 | Result = null,
115 | Exception = new RemoteException(ex)
116 | };
117 | }
118 |
119 | // send a message to the server containing the result.
120 | var message = new Message()
121 | {
122 | MessageType = MessageType.MethodReturnValue,
123 | Data = JsonConvert.SerializeObject(invokeResult, _jsonSerializerSettings)
124 | };
125 | var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message, _jsonSerializerSettings));
126 | await _clientWebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
127 | }
128 | }
129 | else if (receivedMessage.MessageType == MessageType.MethodReturnValue)
130 | {
131 | var invocationResult = JsonConvert.DeserializeObject(receivedMessage.Data, _jsonSerializerSettings);
132 | // find the completion source in the waiting list.
133 | if (_waitingRemoteInvocations.ContainsKey(invocationResult.Identifier))
134 | {
135 | // set the result of the completion source so the invoke method continues executing.
136 | _waitingRemoteInvocations[invocationResult.Identifier].SetResult(invocationResult);
137 | // remove the completion source from the waiting list.
138 | _waitingRemoteInvocations.Remove(invocationResult.Identifier);
139 | }
140 | }
141 | });
142 | }
143 |
144 | ///
145 | /// Send a method invoke request to the server and waits for a reply.
146 | ///
147 | /// Example usage: set the MethodName to SendMessage and set the arguments to the connectionID with a text message
148 | /// An awaitable task with the return value on success.
149 | public async Task SendAsync(InvocationDescriptor invocationDescriptor)
150 | {
151 | // generate a unique identifier for this invocation.
152 | invocationDescriptor.Identifier = Guid.NewGuid();
153 |
154 | // add ourselves to the waiting list for return values.
155 | TaskCompletionSource task = new TaskCompletionSource();
156 | // after a timeout of 60 seconds we will cancel the task and remove it from the waiting list.
157 | new CancellationTokenSource(1000 * 60).Token.Register(() => { _waitingRemoteInvocations.Remove(invocationDescriptor.Identifier); task.TrySetCanceled(); });
158 | _waitingRemoteInvocations.Add(invocationDescriptor.Identifier, task);
159 |
160 | // send the method invocation to the server.
161 | var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Message { MessageType = MessageType.MethodInvocation, Data = JsonConvert.SerializeObject(invocationDescriptor, _jsonSerializerSettings) }, _jsonSerializerSettings));
162 | await _clientWebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
163 |
164 | // wait for the return value elsewhere in the program.
165 | InvocationResult result = await task.Task;
166 |
167 | // ... we just got an answer.
168 |
169 | // if we have completed successfully:
170 | if (task.Task.IsCompleted)
171 | {
172 | // there was a remote exception so we throw it here.
173 | if (result.Exception != null)
174 | throw new Exception(result.Exception.Message);
175 |
176 | // return the value.
177 |
178 | // support null.
179 | if (result.Result == null) return default(T);
180 | // cast anything to T and hope it works.
181 | return (T)result.Result;
182 | }
183 |
184 | // if we reach here we got cancelled or alike so throw a timeout exception.
185 | throw new TimeoutException(); // todo: insert fancy message here.
186 | }
187 |
188 | ///
189 | /// Send a method invoke request to the server.
190 | ///
191 | /// Example usage: set the MethodName to SendMessage and set the arguments to the connectionID with a text message
192 | /// An awaitable task.
193 | public async Task SendAsync(InvocationDescriptor invocationDescriptor)
194 | {
195 | // send the method invocation to the server.
196 | var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Message { MessageType = MessageType.MethodInvocation, Data = JsonConvert.SerializeObject(invocationDescriptor) }));
197 | await _clientWebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
198 | }
199 |
200 | public async Task StopConnectionAsync()
201 | {
202 | await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false);
203 | }
204 |
205 | private async Task Receive(ClientWebSocket clientWebSocket, Action handleMessage)
206 | {
207 | while (_clientWebSocket.State == WebSocketState.Open)
208 | {
209 | ArraySegment buffer = new ArraySegment(new Byte[1024 * 4]);
210 | string serializedMessage = null;
211 | WebSocketReceiveResult result = null;
212 | using (var ms = new MemoryStream())
213 | {
214 | do
215 | {
216 | result = await clientWebSocket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
217 | ms.Write(buffer.Array, buffer.Offset, result.Count);
218 | }
219 | while (!result.EndOfMessage);
220 |
221 | ms.Seek(0, SeekOrigin.Begin);
222 |
223 | using (var reader = new StreamReader(ms, Encoding.UTF8))
224 | {
225 | serializedMessage = await reader.ReadToEndAsync().ConfigureAwait(false);
226 | }
227 | }
228 |
229 | if (result.MessageType == WebSocketMessageType.Text)
230 | {
231 | var message = JsonConvert.DeserializeObject(serializedMessage, _jsonSerializerSettings);
232 | handleMessage(message);
233 | }
234 | else if (result.MessageType == WebSocketMessageType.Close)
235 | {
236 | await _clientWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false);
237 | break;
238 | }
239 | }
240 | }
241 |
242 | public async Task SendOnlyAsync(string method) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { } });
243 |
244 | public async Task SendOnlyAsync(string method, T1 arg1) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1 } });
245 |
246 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2 } });
247 |
248 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3 } });
249 |
250 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4 } });
251 |
252 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5 } });
253 |
254 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6 } });
255 |
256 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 } });
257 |
258 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 } });
259 |
260 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 } });
261 |
262 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 } });
263 |
264 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 } });
265 |
266 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 } });
267 |
268 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13 } });
269 |
270 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 } });
271 |
272 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 } });
273 |
274 | public async Task SendOnlyAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16 } });
275 |
276 | public async Task SendAsync(string method) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { } });
277 |
278 | public async Task SendAsync(string method, T1 arg1) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1 } });
279 |
280 | public async Task SendAsync(string method, T1 arg1, T2 arg2) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2 } });
281 |
282 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3 } });
283 |
284 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4 } });
285 |
286 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5 } });
287 |
288 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6 } });
289 |
290 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 } });
291 |
292 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 } });
293 |
294 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 } });
295 |
296 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 } });
297 |
298 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 } });
299 |
300 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 } });
301 |
302 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13 } });
303 |
304 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 } });
305 |
306 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 } });
307 |
308 | public async Task SendAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) => await SendAsync(new InvocationDescriptor { MethodName = method, Arguments = new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16 } });
309 | }
310 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Client/WebSocketManager.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 0.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Json/JsonBinderWithoutAssembly.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Serialization;
2 | using System;
3 |
4 | namespace WebSocketManager.Common
5 | {
6 | ///
7 | /// Finds types without looking at the assembly.
8 | ///
9 | ///
10 | public class JsonBinderWithoutAssembly : ISerializationBinder
11 | {
12 | public void BindToName(Type serializedType, out string assemblyName, out string typeName)
13 | {
14 | typeName = serializedType.FullName;
15 | assemblyName = null;
16 | }
17 |
18 | public Type BindToType(string assemblyName, string typeName)
19 | {
20 | return Type.GetType(typeName);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Json/PrimitiveJsonConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Runtime.Serialization.Formatters;
4 |
5 | namespace WebSocketManager.Common
6 | {
7 | ///
8 | /// https://stackoverflow.com/questions/25007001/json-net-does-not-preserve-primitive-type-information-in-lists-or-dictionaries-o?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
9 | /// Shoutouts to Sacrilege for his awesome primitive json converter.
10 | ///
11 | ///
12 | public sealed class PrimitiveJsonConverter : JsonConverter
13 | {
14 | public PrimitiveJsonConverter()
15 | {
16 | }
17 |
18 | public override bool CanRead
19 | {
20 | get
21 | {
22 | return false;
23 | }
24 | }
25 |
26 | public override bool CanConvert(Type objectType)
27 | {
28 | return objectType.IsPrimitive || objectType == typeof(Guid) || objectType == typeof(string);
29 | }
30 |
31 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
32 | {
33 | throw new NotImplementedException();
34 | }
35 |
36 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
37 | {
38 | switch (serializer.TypeNameHandling)
39 | {
40 | case TypeNameHandling.All:
41 | writer.WriteStartObject();
42 | writer.WritePropertyName("$type", false);
43 |
44 | switch (serializer.TypeNameAssemblyFormatHandling)
45 | {
46 | case TypeNameAssemblyFormatHandling.Full:
47 | writer.WriteValue(value.GetType().AssemblyQualifiedName);
48 | break;
49 |
50 | default:
51 | writer.WriteValue(value.GetType().FullName);
52 | break;
53 | }
54 |
55 | writer.WritePropertyName("$value", false);
56 | writer.WriteValue(value);
57 | writer.WriteEndObject();
58 | break;
59 |
60 | default:
61 | writer.WriteValue(value);
62 | break;
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Networking/InvocationDescriptor.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace WebSocketManager.Common
5 | {
6 | ///
7 | /// Represents a method name with parameters that is to be executed remotely.
8 | ///
9 | public class InvocationDescriptor
10 | {
11 | ///
12 | /// Gets or sets the name of the remote method.
13 | ///
14 | /// The name of the remote method.
15 | [JsonProperty("methodName")]
16 | public string MethodName { get; set; }
17 |
18 | ///
19 | /// Gets or sets the arguments passed to the method.
20 | ///
21 | /// The arguments passed to the method.
22 | [JsonProperty("arguments")]
23 | public object[] Arguments { get; set; }
24 |
25 | ///
26 | /// Gets or sets the unique identifier used to associate return values with this call.
27 | ///
28 | /// The unique identifier of the invocation.
29 | [JsonProperty("identifier")]
30 | public Guid Identifier { get; set; } = Guid.Empty;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Networking/InvocationResult.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace WebSocketManager.Common
5 | {
6 | ///
7 | /// Represents the return value of a method that was executed remotely.
8 | ///
9 | public class InvocationResult
10 | {
11 | ///
12 | /// Gets or sets the unique identifier associated with the invocation.
13 | ///
14 | /// The unique identifier of the invocation.
15 | [JsonProperty("identifier")]
16 | public Guid Identifier { get; set; }
17 |
18 | ///
19 | /// Gets or sets the result of the method call.
20 | ///
21 | /// The result of the method call.
22 | [JsonProperty("result")]
23 | public object Result { get; set; }
24 |
25 | ///
26 | /// Gets or sets the remote exception the method call caused.
27 | ///
28 | /// The remote exception of the method call.
29 | [JsonProperty("exception")]
30 | public RemoteException Exception { get; set; }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Networking/Message.cs:
--------------------------------------------------------------------------------
1 | namespace WebSocketManager.Common
2 | {
3 | public enum MessageType
4 | {
5 | Text,
6 | MethodInvocation,
7 | ConnectionEvent,
8 | MethodReturnValue
9 | }
10 |
11 | public class Message
12 | {
13 | public MessageType MessageType { get; set; }
14 | public string Data { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Networking/RemoteException.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 |
4 | namespace WebSocketManager.Common
5 | {
6 | ///
7 | /// An exception that occured remotely.
8 | ///
9 | public class RemoteException
10 | {
11 | ///
12 | /// Gets or sets the exception message.
13 | ///
14 | /// The exception message.
15 | [JsonProperty("message")]
16 | public string Message { get; set; } = $"A remote exception occured";
17 |
18 | ///
19 | /// Initializes a new instance of the class.
20 | ///
21 | public RemoteException()
22 | {
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// The exception that occured.
29 | public RemoteException(Exception exception)
30 | {
31 | Message = $"A remote exception occured: '{exception.Message}'.";
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Strategies/ControllerMethodInvocationStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.WebSockets;
5 | using System.Reflection;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebSocketManager.Common
10 | {
11 | ///
12 | /// The controller method invocation strategy. Finds methods in a single class using reflection.
13 | ///
14 | ///
15 | public class ControllerMethodInvocationStrategy : MethodInvocationStrategy
16 | {
17 | ///
18 | /// Gets the controller containing the methods.
19 | ///
20 | /// The controller containing the methods.
21 | public object Controller { get; set; }
22 |
23 | ///
24 | /// Gets the method name prefix. This prevents users from calling methods they aren't
25 | /// supposed to call. You could for example use the awesome 'ᐅ' character.
26 | ///
27 | /// The method name prefix.
28 | public string Prefix { get; } = "";
29 |
30 | ///
31 | /// Gets a value indicating whether there is no websocket argument (useful for client-side methods).
32 | ///
33 | /// true if there is no websocket argument; otherwise, false .
34 | public bool NoWebsocketArgument { get; set; } = false;
35 |
36 | ///
37 | /// Initializes a new instance of the class.
38 | ///
39 | public ControllerMethodInvocationStrategy()
40 | {
41 | }
42 |
43 | ///
44 | /// Initializes a new instance of the class.
45 | ///
46 | /// The controller containing the methods.
47 | public ControllerMethodInvocationStrategy(object controller)
48 | {
49 | Controller = controller;
50 | }
51 |
52 | ///
53 | /// Initializes a new instance of the class.
54 | ///
55 | ///
56 | /// The method name prefix. This prevents users from calling methods they aren't supposed to
57 | /// call. You could for example use the awesome 'ᐅ' character.
58 | ///
59 | /// The controller containing the methods.
60 | public ControllerMethodInvocationStrategy(string prefix, object controller)
61 | {
62 | Prefix = prefix;
63 | Controller = controller;
64 | }
65 |
66 | ///
67 | /// Called when an invoke method call has been received.
68 | ///
69 | /// The web-socket of the client that wants to invoke a method.
70 | ///
71 | /// The invocation descriptor containing the method name and parameters.
72 | ///
73 | /// Awaitable Task.
74 | public override async Task OnInvokeMethodReceivedAsync(WebSocket socket, InvocationDescriptor invocationDescriptor)
75 | {
76 | // create the method name that has to be found.
77 | string command = Prefix + invocationDescriptor.MethodName;
78 |
79 | // use reflection to find the method in the desired controller.
80 | MethodInfo method = Controller.GetType().GetMethod(command);
81 | // if the method could not be found:
82 | if (method == null) throw new Exception($"Received unknown command '{command}' for controller '{Controller.GetType().Name}'.");
83 |
84 | // optionally insert client as parameter.
85 | List args = invocationDescriptor.Arguments.ToList();
86 | if (!NoWebsocketArgument)
87 | args.Insert(0, socket);
88 |
89 | // call the method asynchronously.
90 | try
91 | {
92 | return await Task.Run(() => method.Invoke(Controller, args.ToArray()));
93 | }
94 | catch (TargetInvocationException ex)
95 | {
96 | throw ex.InnerException;
97 | }
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Strategies/DecoratedControllerMethodInvocationStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.WebSockets;
5 | using System.Reflection;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebSocketManager.Common
10 | {
11 | ///
12 | /// The decorated controller method invocation strategy. Finds methods in several classes using reflection.
13 | ///
14 | ///
15 | public class DecoratedControllerMethodInvocationStrategy : MethodInvocationStrategy
16 | {
17 | ///
18 | /// Gets the method name prefix. This prevents users from calling methods they aren't
19 | /// supposed to call. You could for example use the awesome 'ᐅ' character.
20 | ///
21 | /// The method name prefix.
22 | public string Prefix { get; } = "";
23 |
24 | ///
25 | /// Gets the prefix and method name separator. Default value is a forward slash '/'.
26 | ///
27 | /// The separator to separate prefix and method name.
28 | public char Separator { get; } = '/';
29 |
30 | ///
31 | /// Gets a value indicating whether there is no websocket argument (useful for client-side methods).
32 | ///
33 | /// true if there is no websocket argument; otherwise, false .
34 | public bool NoWebsocketArgument { get; set; } = false;
35 |
36 | ///
37 | /// Gets the registered controllers.
38 | ///
39 | /// The registered controllers.
40 | public Dictionary Controllers { get; } = new Dictionary();
41 |
42 | ///
43 | /// Initializes a new instance of the class.
45 | ///
46 | public DecoratedControllerMethodInvocationStrategy()
47 | {
48 | }
49 |
50 | ///
51 | /// Initializes a new instance of the class.
53 | ///
54 | ///
55 | /// The method name prefix. This prevents users from calling methods they aren't supposed to
56 | /// call. You could for example use the awesome 'ᐅ' character.
57 | ///
58 | public DecoratedControllerMethodInvocationStrategy(string prefix)
59 | {
60 | Prefix = prefix;
61 | }
62 |
63 | ///
64 | /// Initializes a new instance of the class.
66 | ///
67 | ///
68 | /// The method name prefix. This prevents users from calling methods they aren't supposed to
69 | /// call. You could for example use the awesome 'ᐅ' character.
70 | ///
71 | /// The prefix and method name separator. Default value is a forward slash '/'.
72 | public DecoratedControllerMethodInvocationStrategy(string prefix, char separator)
73 | {
74 | Prefix = prefix;
75 | Separator = separator;
76 | }
77 |
78 | ///
79 | /// Registers the specified controller to the specified prefix.
80 | ///
81 | /// The controller prefix (e.g. "session").
82 | /// The controller containing the methods.
83 | public void Register(string prefix, object controller)
84 | {
85 | Controllers.Add(prefix.ToLower(), controller);
86 | }
87 |
88 | ///
89 | /// Called when an invoke method call has been received.
90 | ///
91 | /// The web-socket of the client that wants to invoke a method.
92 | ///
93 | /// The invocation descriptor containing the method name and parameters.
94 | ///
95 | /// Awaitable Task.
96 | public override async Task OnInvokeMethodReceivedAsync(WebSocket socket, InvocationDescriptor invocationDescriptor)
97 | {
98 | // there must be a separator in the method name.
99 | if (!invocationDescriptor.MethodName.Contains(Separator)) throw new Exception($"Invalid controller or method name '{invocationDescriptor.MethodName}'.");
100 |
101 | // find the controller and the method name.
102 | string[] names = invocationDescriptor.MethodName.Split(Separator);
103 | string controller = names[0].ToLower();
104 | string command = Prefix + names[1];
105 |
106 | // find the desired controller.
107 | if (Controllers.TryGetValue(controller, out object self))
108 | {
109 | // use reflection to find the method in the desired controller.
110 | MethodInfo method = self.GetType().GetMethod(command);
111 |
112 | // if the method could not be found:
113 | if (method == null)
114 | throw new Exception($"Received unknown command '{command}' for controller '{controller}'.");
115 |
116 | // optionally insert client as parameter.
117 | List args = invocationDescriptor.Arguments.ToList();
118 | if (!NoWebsocketArgument)
119 | args.Insert(0, socket);
120 |
121 | // call the method asynchronously.
122 | try
123 | {
124 | return await Task.Run(() => method.Invoke(self, args.ToArray()));
125 | }
126 | catch (TargetInvocationException ex)
127 | {
128 | throw ex.InnerException;
129 | }
130 | }
131 | else throw new Exception($"Received command '{command}' for unknown controller '{controller}'.");
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Strategies/MethodInvocationStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.WebSockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebSocketManager.Common
9 | {
10 | ///
11 | /// The base class of all method invocation strategies.
12 | ///
13 | public abstract class MethodInvocationStrategy
14 | {
15 | ///
16 | /// Called when an invoke method call has been received.
17 | ///
18 | /// The web-socket of the client that wants to invoke a method.
19 | ///
20 | /// The invocation descriptor containing the method name and parameters.
21 | ///
22 | /// Awaitable Task.
23 | public virtual Task OnInvokeMethodReceivedAsync(WebSocket socket, InvocationDescriptor invocationDescriptor)
24 | {
25 | throw new NotImplementedException();
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/Strategies/StringMethodInvocationStrategy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.WebSockets;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace WebSocketManager.Common
9 | {
10 | ///
11 | /// The string method invocation strategy. Finds methods by registering the names and callbacks.
12 | ///
13 | public class StringMethodInvocationStrategy : MethodInvocationStrategy
14 | {
15 | ///
16 | /// The registered handlers.
17 | ///
18 | private Dictionary _handlers = new Dictionary();
19 |
20 | ///
21 | /// Registers the specified method name and calls the action.
22 | ///
23 | /// Name of the method.
24 | /// The handler action with arguments.
25 | public void On(string methodName, Action handler)
26 | {
27 | var invocationHandler = new InvocationHandler(handler, new Type[] { });
28 | _handlers.Add(methodName, invocationHandler);
29 | }
30 |
31 | ///
32 | /// Registers the specified method name and calls the function.
33 | ///
34 | /// Name of the method.
35 | /// The handler function with arguments and return value.
36 | public void On(string methodName, Func handler)
37 | {
38 | var invocationHandler = new InvocationHandler(handler, new Type[] { });
39 | _handlers.Add(methodName, invocationHandler);
40 | }
41 |
42 | private class InvocationHandler
43 | {
44 | public Func Handler { get; set; }
45 | public Type[] ParameterTypes { get; set; }
46 |
47 | public InvocationHandler(Func handler, Type[] parameterTypes)
48 | {
49 | Handler = handler;
50 | ParameterTypes = parameterTypes;
51 | }
52 |
53 | public InvocationHandler(Action handler, Type[] parameterTypes)
54 | {
55 | Handler = (args) => { handler(args); return null; };
56 | ParameterTypes = parameterTypes;
57 | }
58 | }
59 |
60 | ///
61 | /// Called when an invoke method call has been received.
62 | ///
63 | /// The web-socket of the client that wants to invoke a method.
64 | ///
65 | /// The invocation descriptor containing the method name and parameters.
66 | ///
67 | /// Awaitable Task.
68 | public override async Task OnInvokeMethodReceivedAsync(WebSocket socket, InvocationDescriptor invocationDescriptor)
69 | {
70 | if (!_handlers.ContainsKey(invocationDescriptor.MethodName))
71 | throw new Exception($"Received unknown command '{invocationDescriptor.MethodName}'.");
72 | var invocationHandler = _handlers[invocationDescriptor.MethodName];
73 | if (invocationHandler != null)
74 | return await Task.Run(() => invocationHandler.Handler(invocationDescriptor.Arguments));
75 | return await Task.FromResult(null);
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/src/WebSocketManager.Common/WebSocketManager.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 0.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/WebSocketManager/WebSocketConnectionManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.WebSockets;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebSocketManager
10 | {
11 | public class WebSocketConnectionManager
12 | {
13 | private ConcurrentDictionary _sockets = new ConcurrentDictionary();
14 | private ConcurrentDictionary> _groups = new ConcurrentDictionary>();
15 |
16 | public WebSocket GetSocketById(string id)
17 | {
18 | _sockets.TryGetValue(id, out var socket);
19 | return socket;
20 | }
21 |
22 | public ConcurrentDictionary GetAll()
23 | {
24 | return _sockets;
25 | }
26 |
27 | public List GetAllFromGroup(string GroupID)
28 | {
29 | if (_groups.ContainsKey(GroupID))
30 | {
31 | return _groups[GroupID];
32 | }
33 |
34 | return default(List);
35 | }
36 |
37 | public string GetId(WebSocket socket)
38 | {
39 | return _sockets.FirstOrDefault(p => p.Value == socket).Key;
40 | }
41 |
42 | public void AddSocket(WebSocket socket)
43 | {
44 | _sockets.TryAdd(CreateConnectionId(), socket);
45 | }
46 |
47 | public void AddToGroup(string socketID, string groupID)
48 | {
49 | if (_groups.ContainsKey(groupID))
50 | {
51 | _groups[groupID].Add(socketID);
52 |
53 | return;
54 | }
55 |
56 | _groups.TryAdd(groupID, new List { socketID });
57 | }
58 |
59 | public void RemoveFromGroup(string socketID, string groupID)
60 | {
61 | if (_groups.ContainsKey(groupID))
62 | {
63 | _groups[groupID].Remove(socketID);
64 | }
65 | }
66 |
67 | public async Task RemoveSocket(string id)
68 | {
69 | if (id == null) return;
70 |
71 | _sockets.TryRemove(id, out var socket);
72 |
73 | if (socket.State != WebSocketState.Open) return;
74 |
75 | await socket.CloseAsync(closeStatus: WebSocketCloseStatus.NormalClosure,
76 | statusDescription: "Closed by the WebSocketManager",
77 | cancellationToken: CancellationToken.None).ConfigureAwait(false);
78 | }
79 |
80 | private string CreateConnectionId()
81 | {
82 | return Guid.NewGuid().ToString();
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/WebSocketManager/WebSocketHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.WebSockets;
3 | using System.Text;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using System.Reflection;
7 | using Newtonsoft.Json;
8 | using Newtonsoft.Json.Serialization;
9 | using WebSocketManager.Common;
10 | using System.Collections.Generic;
11 |
12 | namespace WebSocketManager
13 | {
14 | public abstract class WebSocketHandler
15 | {
16 | protected WebSocketConnectionManager WebSocketConnectionManager { get; set; }
17 |
18 | private JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings()
19 | {
20 | ContractResolver = new CamelCasePropertyNamesContractResolver(),
21 | TypeNameHandling = TypeNameHandling.All,
22 | TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
23 | SerializationBinder = new JsonBinderWithoutAssembly()
24 | };
25 |
26 | ///
27 | /// The waiting remote invocations for Server to Client method calls.
28 | ///
29 | private Dictionary> _waitingRemoteInvocations = new Dictionary>();
30 |
31 | ///
32 | /// Gets the method invocation strategy.
33 | ///
34 | /// The method invocation strategy.
35 | public MethodInvocationStrategy MethodInvocationStrategy { get; }
36 |
37 | ///
38 | /// Initializes a new instance of the class.
39 | ///
40 | /// The web socket connection manager.
41 | /// The method invocation strategy used for incoming requests.
42 | public WebSocketHandler(WebSocketConnectionManager webSocketConnectionManager, MethodInvocationStrategy methodInvocationStrategy)
43 | {
44 | _jsonSerializerSettings.Converters.Insert(0, new PrimitiveJsonConverter());
45 | WebSocketConnectionManager = webSocketConnectionManager;
46 | MethodInvocationStrategy = methodInvocationStrategy;
47 | }
48 |
49 | ///
50 | /// Called when a client has connected to the server.
51 | ///
52 | /// The web-socket of the client.
53 | /// Awaitable Task.
54 | public virtual async Task OnConnected(WebSocket socket)
55 | {
56 | WebSocketConnectionManager.AddSocket(socket);
57 |
58 | await SendMessageAsync(socket, new Message()
59 | {
60 | MessageType = MessageType.ConnectionEvent,
61 | Data = WebSocketConnectionManager.GetId(socket)
62 | }).ConfigureAwait(false);
63 | }
64 |
65 | ///
66 | /// Called when a client has disconnected from the server.
67 | ///
68 | /// The web-socket of the client.
69 | /// Awaitable Task.
70 | public virtual async Task OnDisconnected(WebSocket socket)
71 | {
72 | await WebSocketConnectionManager.RemoveSocket(WebSocketConnectionManager.GetId(socket)).ConfigureAwait(false);
73 | }
74 |
75 | public async Task SendMessageAsync(WebSocket socket, Message message)
76 | {
77 | if (socket.State != WebSocketState.Open)
78 | return;
79 |
80 | var serializedMessage = JsonConvert.SerializeObject(message, _jsonSerializerSettings);
81 | var encodedMessage = Encoding.UTF8.GetBytes(serializedMessage);
82 | await socket.SendAsync(buffer: new ArraySegment(array: encodedMessage,
83 | offset: 0,
84 | count: encodedMessage.Length),
85 | messageType: WebSocketMessageType.Text,
86 | endOfMessage: true,
87 | cancellationToken: CancellationToken.None).ConfigureAwait(false);
88 | }
89 |
90 | public async Task SendMessageAsync(string socketId, Message message)
91 | {
92 | await SendMessageAsync(WebSocketConnectionManager.GetSocketById(socketId), message).ConfigureAwait(false);
93 | }
94 |
95 | public async Task SendMessageToAllAsync(Message message)
96 | {
97 | foreach (var pair in WebSocketConnectionManager.GetAll())
98 | {
99 | try
100 | {
101 | if (pair.Value.State == WebSocketState.Open)
102 | await SendMessageAsync(pair.Value, message).ConfigureAwait(false);
103 | }
104 | catch (WebSocketException e)
105 | {
106 | if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
107 | {
108 | await OnDisconnected(pair.Value);
109 | }
110 | }
111 | }
112 | }
113 |
114 | public async Task InvokeClientMethodAsync(string socketId, string methodName, object[] arguments)
115 | {
116 | var message = new Message()
117 | {
118 | MessageType = MessageType.MethodInvocation,
119 | Data = JsonConvert.SerializeObject(new InvocationDescriptor()
120 | {
121 | MethodName = methodName,
122 | Arguments = arguments
123 | }, _jsonSerializerSettings)
124 | };
125 |
126 | await SendMessageAsync(socketId, message).ConfigureAwait(false);
127 | }
128 |
129 | public async Task InvokeClientMethodAsync(string socketId, string methodName, object[] arguments)
130 | {
131 | // create the method invocation descriptor.
132 | InvocationDescriptor invocationDescriptor = new InvocationDescriptor { MethodName = methodName, Arguments = arguments };
133 |
134 | // generate a unique identifier for this invocation.
135 | invocationDescriptor.Identifier = Guid.NewGuid();
136 |
137 | // add ourselves to the waiting list for return values.
138 | TaskCompletionSource task = new TaskCompletionSource();
139 | // after a timeout of 60 seconds we will cancel the task and remove it from the waiting list.
140 | new CancellationTokenSource(1000 * 60).Token.Register(() => { _waitingRemoteInvocations.Remove(invocationDescriptor.Identifier); task.TrySetCanceled(); });
141 | _waitingRemoteInvocations.Add(invocationDescriptor.Identifier, task);
142 |
143 | // send the method invocation to the client.
144 | var message = new Message() { MessageType = MessageType.MethodInvocation, Data = JsonConvert.SerializeObject(invocationDescriptor, _jsonSerializerSettings) };
145 | await SendMessageAsync(socketId, message).ConfigureAwait(false);
146 |
147 | // wait for the return value elsewhere in the program.
148 | InvocationResult result = await task.Task;
149 |
150 | // ... we just got an answer.
151 |
152 | // if we have completed successfully:
153 | if (task.Task.IsCompleted)
154 | {
155 | // there was a remote exception so we throw it here.
156 | if (result.Exception != null)
157 | throw new Exception(result.Exception.Message);
158 |
159 | // return the value.
160 |
161 | // support null.
162 | if (result.Result == null) return default(T);
163 | // cast anything to T and hope it works.
164 | return (T)result.Result;
165 | }
166 |
167 | // if we reach here we got cancelled or alike so throw a timeout exception.
168 | throw new TimeoutException(); // todo: insert fancy message here.
169 | }
170 |
171 | public async Task InvokeClientMethodToAllAsync(string methodName, params object[] arguments)
172 | {
173 | foreach (var pair in WebSocketConnectionManager.GetAll())
174 | {
175 | try
176 | {
177 | if (pair.Value.State == WebSocketState.Open)
178 | await InvokeClientMethodAsync(pair.Key, methodName, arguments).ConfigureAwait(false);
179 | }
180 | catch (WebSocketException e)
181 | {
182 | if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
183 | {
184 | await OnDisconnected(pair.Value);
185 | }
186 | }
187 | }
188 | }
189 |
190 | public async Task SendMessageToGroupAsync(string groupID, Message message)
191 | {
192 | var sockets = WebSocketConnectionManager.GetAllFromGroup(groupID);
193 | if (sockets != null)
194 | {
195 | foreach (var socket in sockets)
196 | {
197 | await SendMessageAsync(socket, message);
198 | }
199 | }
200 | }
201 |
202 | public async Task SendMessageToGroupAsync(string groupID, Message message, string except)
203 | {
204 | var sockets = WebSocketConnectionManager.GetAllFromGroup(groupID);
205 | if (sockets != null)
206 | {
207 | foreach (var id in sockets)
208 | {
209 | if (id != except)
210 | await SendMessageAsync(id, message);
211 | }
212 | }
213 | }
214 |
215 | public async Task InvokeClientMethodToGroupAsync(string groupID, string methodName, params object[] arguments)
216 | {
217 | var sockets = WebSocketConnectionManager.GetAllFromGroup(groupID);
218 | if (sockets != null)
219 | {
220 | foreach (var id in sockets)
221 | {
222 | await InvokeClientMethodAsync(id, methodName, arguments);
223 | }
224 | }
225 | }
226 |
227 | public async Task InvokeClientMethodToGroupAsync(string groupID, string methodName, string except, params object[] arguments)
228 | {
229 | var sockets = WebSocketConnectionManager.GetAllFromGroup(groupID);
230 | if (sockets != null)
231 | {
232 | foreach (var id in sockets)
233 | {
234 | if (id != except)
235 | await InvokeClientMethodAsync(id, methodName, arguments);
236 | }
237 | }
238 | }
239 |
240 | public async Task ReceiveAsync(WebSocket socket, WebSocketReceiveResult result, Message receivedMessage)
241 | {
242 | // method invocation request.
243 | if (receivedMessage.MessageType == MessageType.MethodInvocation)
244 | {
245 | // retrieve the method invocation request.
246 | InvocationDescriptor invocationDescriptor = null;
247 | try
248 | {
249 | invocationDescriptor = JsonConvert.DeserializeObject(receivedMessage.Data, _jsonSerializerSettings);
250 | if (invocationDescriptor == null) return;
251 | }
252 | catch { return; } // ignore invalid data sent to the server.
253 |
254 | // if the unique identifier hasn't been set then the client doesn't want a return value.
255 | if (invocationDescriptor.Identifier == Guid.Empty)
256 | {
257 | // invoke the method only.
258 | try
259 | {
260 | await MethodInvocationStrategy.OnInvokeMethodReceivedAsync(socket, invocationDescriptor);
261 | }
262 | catch (Exception)
263 | {
264 | // we consume all exceptions.
265 | }
266 | }
267 | else
268 | {
269 | // invoke the method and get the result.
270 | InvocationResult invokeResult;
271 | try
272 | {
273 | // create an invocation result with the results.
274 | invokeResult = new InvocationResult()
275 | {
276 | Identifier = invocationDescriptor.Identifier,
277 | Result = await MethodInvocationStrategy.OnInvokeMethodReceivedAsync(socket, invocationDescriptor),
278 | Exception = null
279 | };
280 | }
281 | // send the exception as the invocation result if there was one.
282 | catch (Exception ex)
283 | {
284 | invokeResult = new InvocationResult()
285 | {
286 | Identifier = invocationDescriptor.Identifier,
287 | Result = null,
288 | Exception = new RemoteException(ex)
289 | };
290 | }
291 |
292 | // send a message to the client containing the result.
293 | var message = new Message()
294 | {
295 | MessageType = MessageType.MethodReturnValue,
296 | Data = JsonConvert.SerializeObject(invokeResult, _jsonSerializerSettings)
297 | };
298 | await SendMessageAsync(socket, message).ConfigureAwait(false);
299 | }
300 | }
301 |
302 | // method return value.
303 | else if (receivedMessage.MessageType == MessageType.MethodReturnValue)
304 | {
305 | var invocationResult = JsonConvert.DeserializeObject(receivedMessage.Data, _jsonSerializerSettings);
306 | // find the completion source in the waiting list.
307 | if (_waitingRemoteInvocations.ContainsKey(invocationResult.Identifier))
308 | {
309 | // set the result of the completion source so the invoke method continues executing.
310 | _waitingRemoteInvocations[invocationResult.Identifier].SetResult(invocationResult);
311 | // remove the completion source from the waiting list.
312 | _waitingRemoteInvocations.Remove(invocationResult.Identifier);
313 | }
314 | }
315 | }
316 |
317 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method) => await InvokeClientMethodAsync(socketId, method, new object[] { });
318 |
319 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1 });
320 |
321 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2 });
322 |
323 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3 });
324 |
325 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4 });
326 |
327 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5 });
328 |
329 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
330 |
331 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
332 |
333 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
334 |
335 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
336 |
337 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
338 |
339 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 });
340 |
341 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 });
342 |
343 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13 });
344 |
345 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 });
346 |
347 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 });
348 |
349 | public async Task InvokeClientMethodOnlyAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16 });
350 |
351 | public async Task InvokeClientMethodAsync(string socketId, string method) => await InvokeClientMethodAsync(socketId, method, new object[] { });
352 |
353 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1 });
354 |
355 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2 });
356 |
357 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3 });
358 |
359 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4 });
360 |
361 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5 });
362 |
363 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
364 |
365 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
366 |
367 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
368 |
369 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
370 |
371 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
372 |
373 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 });
374 |
375 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 });
376 |
377 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13 });
378 |
379 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 });
380 |
381 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 });
382 |
383 | public async Task InvokeClientMethodAsync(string socketId, string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) => await InvokeClientMethodAsync(socketId, method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16 });
384 |
385 | public async Task InvokeClientMethodToAllAsync(string method) => await InvokeClientMethodToAllAsync(method, new object[] { });
386 |
387 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1) => await InvokeClientMethodToAllAsync(method, new object[] { arg1 });
388 |
389 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2 });
390 |
391 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3 });
392 |
393 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4 });
394 |
395 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5 });
396 |
397 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6 });
398 |
399 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
400 |
401 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
402 |
403 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
404 |
405 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
406 |
407 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 });
408 |
409 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 });
410 |
411 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13 });
412 |
413 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 });
414 |
415 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 });
416 |
417 | public async Task InvokeClientMethodToAllAsync(string method, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16) => await InvokeClientMethodToAllAsync(method, new object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16 });
418 | }
419 | }
--------------------------------------------------------------------------------
/src/WebSocketManager/WebSocketManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 0.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/WebSocketManager/WebSocketManagerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Microsoft.AspNetCore.Builder;
3 | using Microsoft.AspNetCore.Http;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace WebSocketManager
7 | {
8 | public static class WebSocketManagerExtensions
9 | {
10 | public static IServiceCollection AddWebSocketManager(this IServiceCollection services, Assembly assembly = null)
11 | {
12 | services.AddTransient();
13 |
14 | Assembly ass = assembly ?? Assembly.GetEntryAssembly();
15 |
16 | foreach (var type in ass.ExportedTypes)
17 | {
18 | if (type.GetTypeInfo().BaseType == typeof(WebSocketHandler))
19 | {
20 | services.AddSingleton(type);
21 | }
22 | }
23 |
24 | return services;
25 | }
26 |
27 | public static IApplicationBuilder MapWebSocketManager(this IApplicationBuilder app,
28 | PathString path,
29 | WebSocketHandler handler)
30 | {
31 | return app.Map(path, (_app) => _app.UseMiddleware(handler));
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/WebSocketManager/WebSocketManagerMiddleware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net.WebSockets;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Microsoft.AspNetCore.Http;
8 | using Newtonsoft.Json;
9 | using Newtonsoft.Json.Serialization;
10 | using WebSocketManager.Common;
11 |
12 | namespace WebSocketManager
13 | {
14 | public class WebSocketManagerMiddleware
15 | {
16 | private readonly RequestDelegate _next;
17 | private WebSocketHandler _webSocketHandler { get; set; }
18 |
19 | private JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings()
20 | {
21 | ContractResolver = new CamelCasePropertyNamesContractResolver(),
22 | TypeNameHandling = TypeNameHandling.All,
23 | TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
24 | SerializationBinder = new JsonBinderWithoutAssembly()
25 | };
26 |
27 | public WebSocketManagerMiddleware(RequestDelegate next,
28 | WebSocketHandler webSocketHandler)
29 | {
30 | _jsonSerializerSettings.Converters.Insert(0, new PrimitiveJsonConverter());
31 | _next = next;
32 | _webSocketHandler = webSocketHandler;
33 | }
34 |
35 | public async Task Invoke(HttpContext context)
36 | {
37 | if (!context.WebSockets.IsWebSocketRequest)
38 | {
39 | await _next.Invoke(context);
40 | return;
41 | }
42 |
43 | var socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
44 | await _webSocketHandler.OnConnected(socket).ConfigureAwait(false);
45 |
46 | await Receive(socket, async (result, serializedMessage) =>
47 | {
48 | if (result.MessageType == WebSocketMessageType.Text)
49 | {
50 | Message message = JsonConvert.DeserializeObject(serializedMessage, _jsonSerializerSettings);
51 | await _webSocketHandler.ReceiveAsync(socket, result, message).ConfigureAwait(false);
52 | return;
53 | }
54 | else if (result.MessageType == WebSocketMessageType.Close)
55 | {
56 | try
57 | {
58 | await _webSocketHandler.OnDisconnected(socket);
59 | }
60 | catch (WebSocketException)
61 | {
62 | throw; //let's not swallow any exception for now
63 | }
64 |
65 | return;
66 | }
67 | });
68 | }
69 |
70 | private async Task Receive(WebSocket socket, Action handleMessage)
71 | {
72 | while (socket.State == WebSocketState.Open)
73 | {
74 | ArraySegment buffer = new ArraySegment(new Byte[1024 * 4]);
75 | string message = null;
76 | WebSocketReceiveResult result = null;
77 | try
78 | {
79 | using (var ms = new MemoryStream())
80 | {
81 | do
82 | {
83 | result = await socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
84 | ms.Write(buffer.Array, buffer.Offset, result.Count);
85 | }
86 | while (!result.EndOfMessage);
87 |
88 | ms.Seek(0, SeekOrigin.Begin);
89 |
90 | using (var reader = new StreamReader(ms, Encoding.UTF8))
91 | {
92 | message = await reader.ReadToEndAsync().ConfigureAwait(false);
93 | }
94 | }
95 |
96 | handleMessage(result, message);
97 | }
98 | catch (WebSocketException e)
99 | {
100 | if (e.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
101 | {
102 | socket.Abort();
103 | }
104 | }
105 | }
106 |
107 | await _webSocketHandler.OnDisconnected(socket);
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/test/WebSocketManager.Tests/Helpers/FakeSocket.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.WebSockets;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace WebSocketManager.Tests.Helpers
7 | {
8 | internal class FakeSocket : WebSocket
9 | {
10 | public override void Abort()
11 | {
12 | throw new NotImplementedException();
13 | }
14 |
15 | public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
16 | {
17 | throw new NotImplementedException();
18 | }
19 |
20 | public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
21 | {
22 | throw new NotImplementedException();
23 | }
24 |
25 | public override void Dispose()
26 | {
27 | throw new NotImplementedException();
28 | }
29 |
30 | public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken)
31 | {
32 | throw new NotImplementedException();
33 | }
34 |
35 | public override Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
36 | {
37 | throw new NotImplementedException();
38 | }
39 |
40 | public override WebSocketCloseStatus? CloseStatus { get; }
41 |
42 | public override string CloseStatusDescription { get; }
43 |
44 | public override WebSocketState State { get; }
45 |
46 | public override string SubProtocol { get; }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/WebSocketManager.Tests/WebSocketConnectionManagerTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using WebSocketManager.Tests.Helpers;
3 | using Xunit;
4 |
5 | namespace WebSocketManager.Tests
6 | {
7 | public class WebSocketConnectionManagerTests
8 | {
9 | private readonly WebSocketConnectionManager _manager;
10 |
11 | public WebSocketConnectionManagerTests()
12 | {
13 | _manager = new WebSocketConnectionManager();
14 | }
15 |
16 | public class GetSocketById : WebSocketConnectionManagerTests
17 | {
18 | [Theory]
19 | [InlineData(null)]
20 | [InlineData("")]
21 | [InlineData("foo")]
22 | public void WhenNonExistentId_ShouldReturnNull(string id)
23 | {
24 | var socket = _manager.GetSocketById(id);
25 |
26 | Assert.Null(socket);
27 | }
28 |
29 | [Fact]
30 | public void WhenExistingId_ShouldReturnSocket()
31 | {
32 | var socket = new FakeSocket();
33 |
34 | _manager.AddSocket(socket);
35 | var id = _manager.GetId(socket);
36 |
37 | Assert.Same(socket, _manager.GetSocketById(id));
38 | }
39 | }
40 |
41 | public class GetAll : WebSocketConnectionManagerTests
42 | {
43 | [Fact]
44 | public void WhenEmpty_ShouldReturnZero()
45 | {
46 | Assert.Equal(0, _manager.GetAll().Count);
47 | }
48 |
49 | [Fact]
50 | public void WhenOneSocket_ShouldReturnOne()
51 | {
52 | _manager.AddSocket(new FakeSocket());
53 |
54 | Assert.Equal(1, _manager.GetAll().Count);
55 | }
56 | }
57 |
58 | public class GetAllFromGroup : WebSocketConnectionManagerTests
59 | {
60 | private string GroupName = "FakeGroup";
61 |
62 | [Fact]
63 | public void WhenNonExistingGroup_ShouldReturnNull()
64 | {
65 | Assert.Null(_manager.GetAllFromGroup(GroupName));
66 | }
67 |
68 | [Fact]
69 | public void WhenOneSocketInGroup_ShouldReturnOne()
70 | {
71 | var socket = new FakeSocket();
72 | _manager.AddSocket(socket);
73 | var socketID = _manager.GetId(socket);
74 | _manager.AddToGroup(socketID, GroupName);
75 |
76 | Assert.Equal(1, _manager.GetAllFromGroup(GroupName).Count);
77 | }
78 | }
79 |
80 | public class GetId : WebSocketConnectionManagerTests
81 | {
82 | [Fact]
83 | public void WhenNull_ShouldReturnNull()
84 | {
85 | var id = _manager.GetId(null);
86 |
87 | Assert.Null(id);
88 | }
89 |
90 | [Fact]
91 | public void WhenUntrackedInstance_ShouldReturnNull()
92 | {
93 | var id = _manager.GetId(new FakeSocket());
94 |
95 | Assert.Null(id);
96 | }
97 |
98 | [Fact]
99 | public void WhenTrackedInstance_ShouldReturnId()
100 | {
101 | var socket = new FakeSocket();
102 | _manager.AddSocket(socket);
103 |
104 | var id = _manager.GetId(socket);
105 |
106 | Assert.NotNull(id);
107 | }
108 | }
109 |
110 | public class AddSocket : WebSocketConnectionManagerTests
111 | {
112 | [Fact(Skip = "At the moment the implementation allows adding null references")]
113 | public void WhenNull_ShouldNotNotContainSocket()
114 | {
115 | _manager.AddSocket(null);
116 |
117 | Assert.Equal(0, _manager.GetAll().Count);
118 | }
119 |
120 | [Fact]
121 | public void WhenInstance_ShouldContainSocket()
122 | {
123 | _manager.AddSocket(new FakeSocket());
124 |
125 | Assert.Equal(1, _manager.GetAll().Count);
126 | }
127 | }
128 |
129 | public class RemoveSocket : WebSocketConnectionManagerTests
130 | {
131 | [Theory(Skip = "Currently it doesn't check if the socket was removed or not, so we get an NRE")]
132 | [InlineData(null)]
133 | [InlineData("")]
134 | [InlineData("foo")]
135 | public async Task WhenNonExistentId_ShouldNotThrowException(string id)
136 | {
137 | await _manager.RemoveSocket(id);
138 | }
139 | }
140 |
141 | public class RemoveFromGroup : WebSocketConnectionManagerTests
142 | {
143 | private string GroupName = "FakeGroup";
144 |
145 | [Theory(Skip = "Currently it doesn't check for non existing sockets")]
146 | public void WhenRemoveNonExisting_ShouldNotThrowException()
147 | {
148 | _manager.RemoveFromGroup("", GroupName);
149 | }
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/test/WebSocketManager.Tests/WebSocketManager.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 | 0.2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------