├── Documentation~
├── index.md
└── images
│ ├── node-app-console.png
│ ├── package-manager-add.png
│ ├── package-manager-menu.png
│ ├── server-startup-console.png
│ └── package-manager-selection-dialog.png
├── ChannelClientNodeApp~
├── node_modules
│ ├── async-limiter
│ │ ├── .eslintignore
│ │ ├── .travis.yml
│ │ ├── .nycrc
│ │ ├── LICENSE
│ │ ├── index.js
│ │ ├── package.json
│ │ └── readme.md
│ └── ws
│ │ ├── index.js
│ │ ├── lib
│ │ ├── constants.js
│ │ ├── validation.js
│ │ ├── buffer-util.js
│ │ ├── event-target.js
│ │ ├── extension.js
│ │ ├── websocket-server.js
│ │ ├── sender.js
│ │ ├── receiver.js
│ │ ├── permessage-deflate.js
│ │ └── websocket.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ └── README.md
├── package.json
├── index.html
├── package-lock.json
├── main.js
├── index.js
└── event-service.js
├── .yamato
└── has_moved.md
├── CHANGELOG.md.meta
├── LICENSE.md.meta
├── README.md.meta
├── package.json.meta
├── Editor.meta
├── .gitignore
├── CHANGELOG.md
├── Editor
├── Unity.ChannelClientExamples.Editor.asmdef
├── Unity.ChannelClientExamples.Editor.asmdef.meta
├── ChannelServiceAPI.cs.meta
├── ChannelServiceAPIExample.cs.meta
├── EventServiceDocExample.cs.meta
├── EventServiceExampleWindow.cs.meta
├── ChannelCommunicationDocExample.cs.meta
├── EventServiceExampleWindow.cs
├── EventServiceDocExample.cs
├── ChannelServiceAPIExample.cs
├── ChannelCommunicationDocExample.cs
└── ChannelServiceAPI.cs
├── package.json
├── .npmignore
├── LICENSE.md
└── README.md
/Documentation~/index.md:
--------------------------------------------------------------------------------
1 | # Package documentation guides
2 |
3 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | .nyc_output
--------------------------------------------------------------------------------
/.yamato/has_moved.md:
--------------------------------------------------------------------------------
1 | Please refer to https://github.cds.internal.unity3d.com/unity/upm-ci-yamato-templates
--------------------------------------------------------------------------------
/Documentation~/images/node-app-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Unity-Technologies/com.unity.channel-client-examples/master/Documentation~/images/node-app-console.png
--------------------------------------------------------------------------------
/Documentation~/images/package-manager-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Unity-Technologies/com.unity.channel-client-examples/master/Documentation~/images/package-manager-add.png
--------------------------------------------------------------------------------
/Documentation~/images/package-manager-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Unity-Technologies/com.unity.channel-client-examples/master/Documentation~/images/package-manager-menu.png
--------------------------------------------------------------------------------
/Documentation~/images/server-startup-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Unity-Technologies/com.unity.channel-client-examples/master/Documentation~/images/server-startup-console.png
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ece22e426d63f784e82c0d084bb94467
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f7b70a4f1c1072f46b8a5adb5282cfa1
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 29fd65ce760125347b4a1ca81192df80
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Documentation~/images/package-manager-selection-dialog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Unity-Technologies/com.unity.channel-client-examples/master/Documentation~/images/package-manager-selection-dialog.png
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26685444539b96f44a54356a052eb8fd
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | - "8"
5 | - "10"
6 | - "node"
7 | script: npm run travis
8 | cache:
9 | yarn: true
10 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 05c58485d50fa1e4ebad4a67ee32177d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | artifacts/**
2 | build/**
3 | .build_script/**
4 | node_modules/**
5 | .DS_Store
6 | .npmrc
7 | !Documentation~
8 | !.Documentation
9 | npm-debug.log
10 | build.sh.meta
11 | build.bat.meta
12 | .idea/
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.1.0] - 2020-03-05
4 | ### This is the first release of com.unity.channel-client-examples
5 | Initial release of the package with a node application example and a Unity Handlers.
6 |
7 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "check-coverage": false,
3 | "lines": 99,
4 | "statements": 99,
5 | "functions": 99,
6 | "branches": 99,
7 | "include": [
8 | "index.js"
9 | ]
10 | }
--------------------------------------------------------------------------------
/Editor/Unity.ChannelClientExamples.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.ChannelService.Editor",
3 | "references": [
4 | ],
5 | "includePlatforms": [
6 | "Editor"
7 | ],
8 | "excludePlatforms": []
9 | }
10 |
--------------------------------------------------------------------------------
/Editor/Unity.ChannelClientExamples.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bac489cecddbe2d498ac45970e85400a
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unity-connect-example",
3 | "version": "0.0.1",
4 | "description": "An example of connecting to the ",
5 | "main": "index.js",
6 | "dependencies": {
7 | "ws": "^5.2.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const WebSocket = require('./lib/websocket');
4 |
5 | WebSocket.Server = require('./lib/websocket-server');
6 | WebSocket.Receiver = require('./lib/receiver');
7 | WebSocket.Sender = require('./lib/sender');
8 |
9 | module.exports = WebSocket;
10 |
--------------------------------------------------------------------------------
/Editor/ChannelServiceAPI.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fa789809f10b5e54f820de0f68ed2d52
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.unity.channel-client-examples",
3 | "displayName":"Unity Channel Communication",
4 | "version": "0.1.0-preview",
5 | "unity": "2020.1",
6 | "description": "Provide a public API and examples of using the Unity ChannelService and ChannelClient.",
7 | "dependencies": {
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Editor/ChannelServiceAPIExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fc6863c930a77fe41ab55782c535d96d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/EventServiceDocExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1f8e14691b0516543a3614c54f867c9c
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/EventServiceExampleWindow.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: eb682b1cfc8798c41b1b347d2d7a2e06
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/ChannelCommunicationDocExample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f559317f37ae4b144bfc0bcb36ab2723
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/constants.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
5 | GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
6 | kStatusCode: Symbol('status-code'),
7 | kWebSocket: Symbol('websocket'),
8 | EMPTY_BUFFER: Buffer.alloc(0),
9 | NOOP: () => {}
10 | };
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | artifacts/**
2 | build/**
3 | .build_script/**
4 | node_modules/**
5 | Documentation/ApiDocs/**
6 | Documentation~/ApiDocs/**
7 | .DS_Store
8 | .npmrc
9 | .npmignore
10 | .gitignore
11 | CONTRIBUTING.md
12 | CONTRIBUTING.md.meta
13 | QAReport.md
14 | QAReport.md.meta
15 | .gitlab-ci.yml
16 | build.sh
17 | build.sh.meta
18 | build.bat
19 | build.bat.meta
20 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Test!
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | [com.unity.channel-client-examples] copyright © [2020] Unity Technologies ApS
2 |
3 | Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
4 |
5 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
6 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/validation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | try {
4 | const isValidUTF8 = require('utf-8-validate');
5 |
6 | exports.isValidUTF8 = typeof isValidUTF8 === 'object'
7 | ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
8 | : isValidUTF8;
9 | } catch (e) /* istanbul ignore next */ {
10 | exports.isValidUTF8 = () => true;
11 | }
12 |
13 | /**
14 | * Checks if a status code is allowed in a close frame.
15 | *
16 | * @param {Number} code The status code
17 | * @return {Boolean} `true` if the status code is valid, else `false`
18 | * @public
19 | */
20 | exports.isValidStatusCode = (code) => {
21 | return (
22 | (code >= 1000 &&
23 | code <= 1013 &&
24 | code !== 1004 &&
25 | code !== 1005 &&
26 | code !== 1006) ||
27 | (code >= 3000 && code <= 4999)
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unity-connect-example",
3 | "version": "0.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "async-limiter": {
8 | "version": "1.0.1",
9 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
10 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
11 | },
12 | "ws": {
13 | "version": "5.2.2",
14 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
15 | "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
16 | "requires": {
17 | "async-limiter": "~1.0.0"
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Samuel Reed
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011 Einar Otto Stangvik
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 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function Queue(options) {
4 | if (!(this instanceof Queue)) {
5 | return new Queue(options);
6 | }
7 |
8 | options = options || {};
9 | this.concurrency = options.concurrency || Infinity;
10 | this.pending = 0;
11 | this.jobs = [];
12 | this.cbs = [];
13 | this._done = done.bind(this);
14 | }
15 |
16 | var arrayAddMethods = [
17 | 'push',
18 | 'unshift',
19 | 'splice'
20 | ];
21 |
22 | arrayAddMethods.forEach(function(method) {
23 | Queue.prototype[method] = function() {
24 | var methodResult = Array.prototype[method].apply(this.jobs, arguments);
25 | this._run();
26 | return methodResult;
27 | };
28 | });
29 |
30 | Object.defineProperty(Queue.prototype, 'length', {
31 | get: function() {
32 | return this.pending + this.jobs.length;
33 | }
34 | });
35 |
36 | Queue.prototype._run = function() {
37 | if (this.pending === this.concurrency) {
38 | return;
39 | }
40 | if (this.jobs.length) {
41 | var job = this.jobs.shift();
42 | this.pending++;
43 | job(this._done);
44 | this._run();
45 | }
46 |
47 | if (this.pending === 0) {
48 | while (this.cbs.length !== 0) {
49 | var cb = this.cbs.pop();
50 | process.nextTick(cb);
51 | }
52 | }
53 | };
54 |
55 | Queue.prototype.onDone = function(cb) {
56 | if (typeof cb === 'function') {
57 | this.cbs.push(cb);
58 | this._run();
59 | }
60 | };
61 |
62 | function done() {
63 | this.pending--;
64 | this._run();
65 | }
66 |
67 | module.exports = Queue;
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Channel Client Examples
2 |
3 | ## Code Walkthrough
4 | - Make a public API for the ChannelService (currently internal). See `Editor/ChannelServiceAPI.cs`
5 | - Currently in trunk all ChannelService API are internals.
6 | - Make an example implementing 2 new routes, one binary and the other using string. See See `Editor/ChannelServiceExample.cs`
7 | - This adds a new menu item: `Tools/Register Custom Ping Pong Channels` that can be use to restart the server and register all listeners.
8 | - `ChannelClientNodeApp~` contains a node application that will connect to Unity on the specified routes and send a bit of data.
9 |
10 | ## How to connect?
11 |
12 | - By default, if you use this package the `ChannelService` will be started (basically the Websocket server will be listening for connection) automatically and some handlers will be registered each domain reload.
13 | - The connection address in is : 127.0.0.1:XXXX. Connecting port is dynamic (!). *This should be streamlined...*
14 | - To bypass the fact that the port is dynamic, the StartChannelService function of `ChannelServiceAPI` creates a file `/Local/Unity/Editor/ChannelService.info` and writes the address and port in that file.
15 |
16 | ## The Demo
17 |
18 | 1) Start Unity
19 | 2) Ensure you add the `com.unity.channelservice` package.
20 |
21 | 
22 | 
23 | 
24 |
25 | 3) See that the console prints these messages:
26 |
27 | 
28 |
29 | 4) Install [nodejs](https://nodejs.org/)
30 |
31 | 5) Open a command console and be sure to `cd` to the `ChannelClientNodeApp~`. Type `node index.js`.
32 |
33 | 6) You should see the following:
34 |
35 | 
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/main.js:
--------------------------------------------------------------------------------
1 | import * as eventService from "./event-service.js"
2 |
3 | const kWebEmit1 = "webEmit1";
4 | const kWebRequest1 = "webRequest1";
5 | const kWebRequest2 = "webRequest2";
6 | const kUnityEmit1 = "unityEmit1";
7 | const kUnityRequest1 = "unityRequest1";
8 |
9 | // Setup UI
10 | document.getElementById("emit1").addEventListener("click", OnEmit1);
11 | document.getElementById("request1").addEventListener("click", OnRequest1);
12 | document.getElementById("request2").addEventListener("click", OnRequest2);
13 | document.getElementById("log1").addEventListener("click", OnLog1);
14 |
15 | // Setup EventService
16 | eventService.Start(54209);
17 | let unityEmit1OffHandler = eventService.On(kUnityEmit1, (eventType, data) => {
18 | console.log(`On ${kUnityEmit1}: [${eventType}] ${data.join(",")}`)
19 | unityEmit1OffHandler();
20 | });
21 | let unityRequest1OffHandler = eventService.On(kUnityRequest1, (eventType, data) => {
22 | console.log(`On ${kUnityRequest1}: [${eventType}] ${data.join(",")}`)
23 | return [1, "2", 3.0];
24 | });
25 |
26 | function OnEmit1()
27 | {
28 | eventService.Emit(kWebEmit1, [1, "2", 3.0]);
29 | }
30 |
31 | function OnRequest1()
32 | {
33 | eventService.Request(kWebRequest1, (err, data) => {
34 | if (err) {
35 | console.error(err);
36 | return;
37 | }
38 | console.log(`On receiving ${kWebRequest1}: ${data.join(",")}`);
39 | }, [1, "2", 3.0]);
40 | }
41 |
42 | function OnRequest2()
43 | {
44 | eventService.Request(kWebRequest2, (err, data) => {
45 | if (!err) {
46 | console.error(`On ${kWebRequest2}: there should have been an error.`);
47 | return;
48 | }
49 | console.log(err);
50 | }, ["123"], 100)
51 | }
52 |
53 | function OnLog1()
54 | {
55 | eventService.Log("This tests the EventService.Log() function.");
56 | }
57 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/index.js:
--------------------------------------------------------------------------------
1 | const WebSocket = require('ws');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | const channelServiceInfoFile = path.join(process.env.LOCALAPPDATA, "Unity", "Editor", "ChannelService.info");
6 |
7 | function unityChannelConnect(name, isBinary, readyForConnectionCallback)
8 | {
9 | if (!fs.existsSync(channelServiceInfoFile))
10 | throw `${channelServiceInfoFile} doesn't exists. ChannelService is not started.`
11 |
12 | const addr = fs.readFileSync(channelServiceInfoFile);
13 | const connectTo = `ws://${addr}/${name}`;
14 | console.log("Trying to connect to " + connectTo);
15 |
16 | // Note that the port is dynamic...
17 | const socket = new WebSocket(connectTo);
18 | if (isBinary)
19 | socket.binaryType = 'arraybuffer';
20 |
21 | const binStatus = isBinary ? "binary" : "";
22 |
23 | var isReady = false;
24 |
25 | // Connection opened
26 | socket.addEventListener('open', function (event) {
27 | console.log(`[${name}] Connected ${binStatus}`);
28 | });
29 |
30 | socket.addEventListener('close', function (event) {
31 | console.log(`[${name}] Closed ${binStatus}`);
32 | });
33 |
34 | // Listen for messages
35 | socket.addEventListener('message', function (event) {
36 | if (!isReady)
37 | {
38 | isReady = true;
39 | readyForConnectionCallback();
40 | }
41 |
42 | if (isBinary)
43 | console.log(`[${name} - binary] ${event.data}`);
44 | else
45 | console.log(`[${name}] ${event.data}`);
46 | });
47 |
48 | return socket;
49 | }
50 |
51 | var binaryConnection = unityChannelConnect("custom_binary_ping_pong", true, () => {
52 | const array = new Int32Array(5);
53 | for (var i = 0; i < array.length; ++i) {
54 | array[i] = i;
55 | }
56 | binaryConnection.send(array);
57 | });
58 |
59 | var stringConnection = unityChannelConnect("custom_ascii_ping_pong", false, () => {
60 | stringConnection.send("hello ascii world!!!");
61 | });
--------------------------------------------------------------------------------
/Editor/EventServiceExampleWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Unity.MPE;
3 | using UnityEditor;
4 | using UnityEngine;
5 | using UnityEngine.UIElements;
6 |
7 | public class EventServiceExampleWindow : EditorWindow
8 | {
9 | const string k_WebEmit1 = "webEmit1";
10 | const string k_WebRequest1 = "webRequest1";
11 | const string k_UnityEmit1 = "unityEmit1";
12 | const string k_UnityRequest1 = "unityRequest1";
13 |
14 | Action m_WebEmit1Off = () => { };
15 | Action m_WebRequest1Off = () => { };
16 |
17 | [MenuItem("Tools/Open EventServiceExample Window")]
18 | static void Init()
19 | {
20 | GetWindow();
21 | }
22 |
23 | void OnEnable()
24 | {
25 | m_WebEmit1Off = EventService.On(k_WebEmit1, (type, data) =>
26 | {
27 | Debug.Log($"On {k_WebEmit1}: [{type}] {string.Join(",", data)}");
28 | });
29 |
30 | m_WebRequest1Off = EventService.On(k_WebRequest1, (type, data) =>
31 | {
32 | Debug.Log($"On {k_WebRequest1}: [{type}] {string.Join(",", data)}");
33 | return new object[] { "test", 42, 123.4f };
34 | });
35 |
36 | var emit1Button = new Button(EmitUnity1);
37 | emit1Button.text = "Test Emit 1";
38 | rootVisualElement.Add(emit1Button);
39 |
40 | var request1Button = new Button(RequestUnity1);
41 | request1Button.text = "Test Request 1";
42 | rootVisualElement.Add(request1Button);
43 | }
44 |
45 | void OnDisable()
46 | {
47 | m_WebEmit1Off();
48 | m_WebRequest1Off();
49 | }
50 |
51 | static void EmitUnity1()
52 | {
53 | EventService.Emit(k_UnityEmit1, new object[] {"test", 42});
54 | }
55 |
56 | static void RequestUnity1()
57 | {
58 | EventService.Request(k_UnityRequest1, (err, data) =>
59 | {
60 | if (err != null)
61 | {
62 | Debug.LogException(err);
63 | return;
64 | }
65 | Debug.Log($"On receiving {k_UnityRequest1}: {string.Join(",", data)}");
66 | }, new object[] { "test", 42 });
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/buffer-util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Merges an array of buffers into a new buffer.
5 | *
6 | * @param {Buffer[]} list The array of buffers to concat
7 | * @param {Number} totalLength The total length of buffers in the list
8 | * @return {Buffer} The resulting buffer
9 | * @public
10 | */
11 | function concat (list, totalLength) {
12 | const target = Buffer.allocUnsafe(totalLength);
13 | var offset = 0;
14 |
15 | for (var i = 0; i < list.length; i++) {
16 | const buf = list[i];
17 | buf.copy(target, offset);
18 | offset += buf.length;
19 | }
20 |
21 | return target;
22 | }
23 |
24 | /**
25 | * Masks a buffer using the given mask.
26 | *
27 | * @param {Buffer} source The buffer to mask
28 | * @param {Buffer} mask The mask to use
29 | * @param {Buffer} output The buffer where to store the result
30 | * @param {Number} offset The offset at which to start writing
31 | * @param {Number} length The number of bytes to mask.
32 | * @public
33 | */
34 | function _mask (source, mask, output, offset, length) {
35 | for (var i = 0; i < length; i++) {
36 | output[offset + i] = source[i] ^ mask[i & 3];
37 | }
38 | }
39 |
40 | /**
41 | * Unmasks a buffer using the given mask.
42 | *
43 | * @param {Buffer} buffer The buffer to unmask
44 | * @param {Buffer} mask The mask to use
45 | * @public
46 | */
47 | function _unmask (buffer, mask) {
48 | // Required until https://github.com/nodejs/node/issues/9006 is resolved.
49 | const length = buffer.length;
50 | for (var i = 0; i < length; i++) {
51 | buffer[i] ^= mask[i & 3];
52 | }
53 | }
54 |
55 | try {
56 | const bufferUtil = require('bufferutil');
57 | const bu = bufferUtil.BufferUtil || bufferUtil;
58 |
59 | module.exports = {
60 | mask (source, mask, output, offset, length) {
61 | if (length < 48) _mask(source, mask, output, offset, length);
62 | else bu.mask(source, mask, output, offset, length);
63 | },
64 | unmask (buffer, mask) {
65 | if (buffer.length < 32) _unmask(buffer, mask);
66 | else bu.unmask(buffer, mask);
67 | },
68 | concat
69 | };
70 | } catch (e) /* istanbul ignore next */ {
71 | module.exports = { concat, mask: _mask, unmask: _unmask };
72 | }
73 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "async-limiter@1.0.1",
5 | "D:\\work\\code\\com.unity.channelservice\\ChannelClientNodeApp~"
6 | ]
7 | ],
8 | "_from": "async-limiter@1.0.1",
9 | "_id": "async-limiter@1.0.1",
10 | "_inBundle": false,
11 | "_integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
12 | "_location": "/async-limiter",
13 | "_phantomChildren": {},
14 | "_requested": {
15 | "type": "version",
16 | "registry": true,
17 | "raw": "async-limiter@1.0.1",
18 | "name": "async-limiter",
19 | "escapedName": "async-limiter",
20 | "rawSpec": "1.0.1",
21 | "saveSpec": null,
22 | "fetchSpec": "1.0.1"
23 | },
24 | "_requiredBy": [
25 | "/ws"
26 | ],
27 | "_resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
28 | "_spec": "1.0.1",
29 | "_where": "D:\\work\\code\\com.unity.channelservice\\ChannelClientNodeApp~",
30 | "author": {
31 | "name": "Samuel Reed"
32 | },
33 | "bugs": {
34 | "url": "https://github.com/strml/async-limiter/issues"
35 | },
36 | "dependencies": {},
37 | "description": "asynchronous function queue with adjustable concurrency",
38 | "devDependencies": {
39 | "coveralls": "^3.0.3",
40 | "eslint": "^5.16.0",
41 | "eslint-plugin-mocha": "^5.3.0",
42 | "intelli-espower-loader": "^1.0.1",
43 | "mocha": "^6.1.4",
44 | "nyc": "^14.1.1",
45 | "power-assert": "^1.6.1"
46 | },
47 | "homepage": "https://github.com/strml/async-limiter#readme",
48 | "keywords": [
49 | "throttle",
50 | "async",
51 | "limiter",
52 | "asynchronous",
53 | "job",
54 | "task",
55 | "concurrency",
56 | "concurrent"
57 | ],
58 | "license": "MIT",
59 | "name": "async-limiter",
60 | "repository": {
61 | "type": "git",
62 | "url": "git+https://github.com/strml/async-limiter.git"
63 | },
64 | "scripts": {
65 | "coverage": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
66 | "example": "node example",
67 | "lint": "eslint .",
68 | "test": "mocha --require intelli-espower-loader test/",
69 | "travis": "npm run lint && npm run test"
70 | },
71 | "version": "1.0.1"
72 | }
73 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "_args": [
3 | [
4 | "ws@5.2.2",
5 | "D:\\work\\code\\com.unity.channelservice\\ChannelClientNodeApp~"
6 | ]
7 | ],
8 | "_from": "ws@5.2.2",
9 | "_id": "ws@5.2.2",
10 | "_inBundle": false,
11 | "_integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
12 | "_location": "/ws",
13 | "_phantomChildren": {},
14 | "_requested": {
15 | "type": "version",
16 | "registry": true,
17 | "raw": "ws@5.2.2",
18 | "name": "ws",
19 | "escapedName": "ws",
20 | "rawSpec": "5.2.2",
21 | "saveSpec": null,
22 | "fetchSpec": "5.2.2"
23 | },
24 | "_requiredBy": [
25 | "/"
26 | ],
27 | "_resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
28 | "_spec": "5.2.2",
29 | "_where": "D:\\work\\code\\com.unity.channelservice\\ChannelClientNodeApp~",
30 | "author": {
31 | "name": "Einar Otto Stangvik",
32 | "email": "einaros@gmail.com",
33 | "url": "http://2x.io"
34 | },
35 | "bugs": {
36 | "url": "https://github.com/websockets/ws/issues"
37 | },
38 | "dependencies": {
39 | "async-limiter": "~1.0.0"
40 | },
41 | "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
42 | "devDependencies": {
43 | "benchmark": "~2.1.2",
44 | "bufferutil": "~3.0.0",
45 | "eslint": "~4.19.0",
46 | "eslint-config-standard": "~11.0.0",
47 | "eslint-plugin-import": "~2.12.0",
48 | "eslint-plugin-node": "~6.0.0",
49 | "eslint-plugin-promise": "~3.8.0",
50 | "eslint-plugin-standard": "~3.0.0",
51 | "mocha": "~5.2.0",
52 | "nyc": "~12.0.2",
53 | "utf-8-validate": "~4.0.0"
54 | },
55 | "files": [
56 | "index.js",
57 | "lib"
58 | ],
59 | "homepage": "https://github.com/websockets/ws",
60 | "keywords": [
61 | "HyBi",
62 | "Push",
63 | "RFC-6455",
64 | "WebSocket",
65 | "WebSockets",
66 | "real-time"
67 | ],
68 | "license": "MIT",
69 | "main": "index.js",
70 | "name": "ws",
71 | "repository": {
72 | "type": "git",
73 | "url": "git+https://github.com/websockets/ws.git"
74 | },
75 | "scripts": {
76 | "integration": "eslint . && mocha test/*.integration.js",
77 | "lint": "eslint .",
78 | "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js"
79 | },
80 | "version": "5.2.2"
81 | }
82 |
--------------------------------------------------------------------------------
/Editor/EventServiceDocExample.cs:
--------------------------------------------------------------------------------
1 | // #define COMMUNICATION_PUBLIC_API
2 | #if COMMUNICATION_PUBLIC_API
3 | using UnityEditor;
4 | using UnityEngine;
5 | using Unity.MPE;
6 | using System;
7 |
8 | public static class EventServiceDocExample
9 | {
10 | static Action s_CustomLogEventDisconnect;
11 | static Action s_PingPongEventDisconnect;
12 |
13 | [MenuItem("EventServiceDoc/Step 0")]
14 | static void StartChannelService()
15 | {
16 | if (!ChannelService.IsRunning())
17 | {
18 | ChannelService.Start();
19 | }
20 | Debug.Log($"[Step 0] ChannelService Running: {ChannelService.GetAddress()}:{ChannelService.GetPort()}");
21 | }
22 |
23 | [MenuItem("EventServiceDoc/Step 1")]
24 | static void SetupEventServiceHandlers()
25 | {
26 | Debug.Log("[Step 1] Setup handlers");
27 | s_CustomLogEventDisconnect = EventService.On("custom_log", (eventType, args) => {
28 | Debug.Log($"Log a {eventType} {args[0]}");
29 | });
30 |
31 | s_PingPongEventDisconnect = EventService.On("pingpong", (eventType, args) =>
32 | {
33 | Debug.Log($"Receive a {eventType} {args[0]}");
34 | return "pong!";
35 | });
36 | }
37 |
38 | [MenuItem("EventServiceDoc/Step 2")]
39 | static void EmitMessage()
40 | {
41 | Debug.Log("[Step 2] Emitting a custom log");
42 | EventService.Emit("custom_log", "Hello world!", -1, EventDataSerialization.JsonUtility);
43 | }
44 |
45 | [MenuItem("EventServiceDoc/Step 3")]
46 | static void SendRequest()
47 | {
48 | Debug.Log("[Step 3] Sending a request");
49 | EventService.Request("pingpong", (err, data) =>
50 | {
51 | Debug.Log($"Request fulfilled: {data[0]}");
52 | },
53 | "ping", -1, EventDataSerialization.JsonUtility);
54 | }
55 |
56 | [MenuItem("EventServiceDoc/Step 4")]
57 | static void CloseHandlers()
58 | {
59 | Debug.Log("[Step 4] Closing all Event handlers");
60 | s_CustomLogEventDisconnect();
61 | s_PingPongEventDisconnect();
62 | }
63 | }
64 |
65 | /*
66 |
67 | If you execute the 5 menu item one after the other, this will print the following
68 | in the console:
69 |
70 | [Step 0] ChannelService Running: 127.0.0.1:65000
71 |
72 | [Step 1] Setup handlers
73 |
74 | [Step 2] Emitting a custom log
75 |
76 | Log a custom_log Hello world!
77 |
78 | [Step 3] Sending a request
79 |
80 | Receive a pingpong ping
81 |
82 | Request fulfilled: pong!
83 |
84 | [Step 4] Closing all Event handlers
85 |
86 | */
87 | #endif
--------------------------------------------------------------------------------
/Editor/ChannelServiceAPIExample.cs:
--------------------------------------------------------------------------------
1 | // #define REGISTER_CHANNEL_ON_STARTUP
2 | using System;
3 | using System.Collections;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Text;
8 | using Unity.MPE;
9 | using UnityEditor;
10 | using UnityEngine;
11 |
12 | static class ChannelAPIExample
13 | {
14 | static int s_BinaryChannelId;
15 | static int s_StringChannelId;
16 | static Action s_DisconnectBinaryChannel;
17 | static Action s_DisconnectStringChannel;
18 |
19 | #if REGISTER_CHANNEL_ON_STARTUP
20 | // This function is called each domain reload (i.e each time a script recompiles).
21 | [InitializeOnLoadMethod]
22 | static void RegisterChannelOnLoad()
23 | {
24 | RegisterChannelService();
25 | }
26 | #endif
27 |
28 | static void RegisterChannelService()
29 | {
30 | if (!ChannelServiceAPI.IsRunning())
31 | ChannelServiceAPI.StartChannelService();
32 |
33 | Debug.Log($"ChannelService Running: {ChannelServiceAPI.GetAddress()}:{ChannelServiceAPI.GetPort()}");
34 |
35 | if (s_DisconnectBinaryChannel == null)
36 | {
37 | s_DisconnectBinaryChannel = ChannelServiceAPI.GetOrCreateChannel("custom_binary_ping_pong", HandleChannelBinaryMessage);
38 | s_BinaryChannelId = ChannelServiceAPI.GetChannelId("custom_binary_ping_pong");
39 | Debug.Log($"channel_custom_binary id: {s_BinaryChannelId}");
40 | }
41 |
42 | if (s_DisconnectStringChannel == null)
43 | {
44 | s_DisconnectStringChannel = ChannelServiceAPI.GetOrCreateChannel("custom_ascii_ping_pong", HandleChannelStringMessage);
45 | s_StringChannelId = ChannelServiceAPI.GetChannelId("custom_ascii_ping_pong");
46 | Debug.Log($"channel_custom_ascii id: {s_StringChannelId}");
47 | }
48 | }
49 |
50 | [MenuItem("ChannelServiceAPI/Register new channels")]
51 | static void RegisterMenu()
52 | {
53 | RegisterChannelService();
54 | }
55 |
56 | static void HandleChannelBinaryMessage(int connectionId, byte[] data)
57 | {
58 | var msg = "";
59 | for (var i = 0; i < Math.Min(10, data.Length); ++i)
60 | {
61 | msg += data[i].ToString();
62 | }
63 | Debug.Log($"receiving binary from connection {connectionId} - {data.Length} bytes - {msg}");
64 |
65 | // Let's pong it back:
66 | ChannelServiceAPI.SendBinary(connectionId, data);
67 | }
68 |
69 | static void HandleChannelStringMessage(int connectionId, byte[] data)
70 | {
71 | var msgStr = Encoding.UTF8.GetString(data);
72 | Debug.Log($"receiving string from connection {connectionId} - {msgStr}");
73 |
74 | // Let's pong it back:
75 | ChannelServiceAPI.Send(connectionId, msgStr);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/async-limiter/readme.md:
--------------------------------------------------------------------------------
1 | # Async-Limiter
2 |
3 | A module for limiting concurrent asynchronous actions in flight. Forked from [queue](https://github.com/jessetane/queue).
4 |
5 | [](http://www.npmjs.org/async-limiter)
6 | [](https://travis-ci.org/STRML/async-limiter)
7 | [](https://coveralls.io/r/STRML/async-limiter)
8 |
9 | This module exports a class `Limiter` that implements some of the `Array` API.
10 | Pass async functions (ones that accept a callback or return a promise) to an instance's additive array methods.
11 |
12 | ## Motivation
13 |
14 | Certain functions, like `zlib`, have [undesirable behavior](https://github.com/nodejs/node/issues/8871#issuecomment-250915913) when
15 | run at infinite concurrency.
16 |
17 | In this case, it is actually faster, and takes far less memory, to limit concurrency.
18 |
19 | This module should do the absolute minimum work necessary to queue up functions. PRs are welcome that would
20 | make this module faster or lighter, but new functionality is not desired.
21 |
22 | Style should confirm to nodejs/node style.
23 |
24 | ## Example
25 |
26 | ``` javascript
27 | var Limiter = require('async-limiter')
28 |
29 | var t = new Limiter({concurrency: 2});
30 | var results = []
31 |
32 | // add jobs using the familiar Array API
33 | t.push(function (cb) {
34 | results.push('two')
35 | cb()
36 | })
37 |
38 | t.push(
39 | function (cb) {
40 | results.push('four')
41 | cb()
42 | },
43 | function (cb) {
44 | results.push('five')
45 | cb()
46 | }
47 | )
48 |
49 | t.unshift(function (cb) {
50 | results.push('one')
51 | cb()
52 | })
53 |
54 | t.splice(2, 0, function (cb) {
55 | results.push('three')
56 | cb()
57 | })
58 |
59 | // Jobs run automatically. If you want a callback when all are done,
60 | // call 'onDone()'.
61 | t.onDone(function () {
62 | console.log('all done:', results)
63 | })
64 | ```
65 |
66 | ## Zlib Example
67 |
68 | ```js
69 | const zlib = require('zlib');
70 | const Limiter = require('async-limiter');
71 |
72 | const message = {some: "data"};
73 | const payload = new Buffer(JSON.stringify(message));
74 |
75 | // Try with different concurrency values to see how this actually
76 | // slows significantly with higher concurrency!
77 | //
78 | // 5: 1398.607ms
79 | // 10: 1375.668ms
80 | // Infinity: 4423.300ms
81 | //
82 | const t = new Limiter({concurrency: 5});
83 | function deflate(payload, cb) {
84 | t.push(function(done) {
85 | zlib.deflate(payload, function(err, buffer) {
86 | done();
87 | cb(err, buffer);
88 | });
89 | });
90 | }
91 |
92 | console.time('deflate');
93 | for(let i = 0; i < 30000; ++i) {
94 | deflate(payload, function (err, buffer) {});
95 | }
96 | t.onDone(function() {
97 | console.timeEnd('deflate');
98 | });
99 | ```
100 |
101 | ## Install
102 |
103 | `npm install async-limiter`
104 |
105 | ## Test
106 |
107 | `npm test`
108 |
109 | ## API
110 |
111 | ### `var t = new Limiter([opts])`
112 | Constructor. `opts` may contain inital values for:
113 | * `t.concurrency`
114 |
115 | ## Instance methods
116 |
117 | ### `t.onDone(fn)`
118 | `fn` will be called once and only once, when the queue is empty.
119 |
120 | ## Instance methods mixed in from `Array`
121 | Mozilla has docs on how these methods work [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).
122 | ### `t.push(element1, ..., elementN)`
123 | ### `t.unshift(element1, ..., elementN)`
124 | ### `t.splice(index , howMany[, element1[, ...[, elementN]]])`
125 |
126 | ## Properties
127 | ### `t.concurrency`
128 | Max number of jobs the queue should process concurrently, defaults to `Infinity`.
129 |
130 | ### `t.length`
131 | Jobs pending + jobs to process (readonly).
132 |
133 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/event-target.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Class representing an event.
5 | *
6 | * @private
7 | */
8 | class Event {
9 | /**
10 | * Create a new `Event`.
11 | *
12 | * @param {String} type The name of the event
13 | * @param {Object} target A reference to the target to which the event was dispatched
14 | */
15 | constructor (type, target) {
16 | this.target = target;
17 | this.type = type;
18 | }
19 | }
20 |
21 | /**
22 | * Class representing a message event.
23 | *
24 | * @extends Event
25 | * @private
26 | */
27 | class MessageEvent extends Event {
28 | /**
29 | * Create a new `MessageEvent`.
30 | *
31 | * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
32 | * @param {WebSocket} target A reference to the target to which the event was dispatched
33 | */
34 | constructor (data, target) {
35 | super('message', target);
36 |
37 | this.data = data;
38 | }
39 | }
40 |
41 | /**
42 | * Class representing a close event.
43 | *
44 | * @extends Event
45 | * @private
46 | */
47 | class CloseEvent extends Event {
48 | /**
49 | * Create a new `CloseEvent`.
50 | *
51 | * @param {Number} code The status code explaining why the connection is being closed
52 | * @param {String} reason A human-readable string explaining why the connection is closing
53 | * @param {WebSocket} target A reference to the target to which the event was dispatched
54 | */
55 | constructor (code, reason, target) {
56 | super('close', target);
57 |
58 | this.wasClean = target._closeFrameReceived && target._closeFrameSent;
59 | this.reason = reason;
60 | this.code = code;
61 | }
62 | }
63 |
64 | /**
65 | * Class representing an open event.
66 | *
67 | * @extends Event
68 | * @private
69 | */
70 | class OpenEvent extends Event {
71 | /**
72 | * Create a new `OpenEvent`.
73 | *
74 | * @param {WebSocket} target A reference to the target to which the event was dispatched
75 | */
76 | constructor (target) {
77 | super('open', target);
78 | }
79 | }
80 |
81 | /**
82 | * Class representing an error event.
83 | *
84 | * @extends Event
85 | * @private
86 | */
87 | class ErrorEvent extends Event {
88 | /**
89 | * Create a new `ErrorEvent`.
90 | *
91 | * @param {Object} error The error that generated this event
92 | * @param {WebSocket} target A reference to the target to which the event was dispatched
93 | */
94 | constructor (error, target) {
95 | super('error', target);
96 |
97 | this.message = error.message;
98 | this.error = error;
99 | }
100 | }
101 |
102 | /**
103 | * This provides methods for emulating the `EventTarget` interface. It's not
104 | * meant to be used directly.
105 | *
106 | * @mixin
107 | */
108 | const EventTarget = {
109 | /**
110 | * Register an event listener.
111 | *
112 | * @param {String} method A string representing the event type to listen for
113 | * @param {Function} listener The listener to add
114 | * @public
115 | */
116 | addEventListener (method, listener) {
117 | if (typeof listener !== 'function') return;
118 |
119 | function onMessage (data) {
120 | listener.call(this, new MessageEvent(data, this));
121 | }
122 |
123 | function onClose (code, message) {
124 | listener.call(this, new CloseEvent(code, message, this));
125 | }
126 |
127 | function onError (error) {
128 | listener.call(this, new ErrorEvent(error, this));
129 | }
130 |
131 | function onOpen () {
132 | listener.call(this, new OpenEvent(this));
133 | }
134 |
135 | if (method === 'message') {
136 | onMessage._listener = listener;
137 | this.on(method, onMessage);
138 | } else if (method === 'close') {
139 | onClose._listener = listener;
140 | this.on(method, onClose);
141 | } else if (method === 'error') {
142 | onError._listener = listener;
143 | this.on(method, onError);
144 | } else if (method === 'open') {
145 | onOpen._listener = listener;
146 | this.on(method, onOpen);
147 | } else {
148 | this.on(method, listener);
149 | }
150 | },
151 |
152 | /**
153 | * Remove an event listener.
154 | *
155 | * @param {String} method A string representing the event type to remove
156 | * @param {Function} listener The listener to remove
157 | * @public
158 | */
159 | removeEventListener (method, listener) {
160 | const listeners = this.listeners(method);
161 |
162 | for (var i = 0; i < listeners.length; i++) {
163 | if (listeners[i] === listener || listeners[i]._listener === listener) {
164 | this.removeListener(method, listeners[i]);
165 | }
166 | }
167 | }
168 | };
169 |
170 | module.exports = EventTarget;
171 |
--------------------------------------------------------------------------------
/Editor/ChannelCommunicationDocExample.cs:
--------------------------------------------------------------------------------
1 | // #define COMMUNICATION_PUBLIC_API
2 | #if COMMUNICATION_PUBLIC_API
3 | using System;
4 | using System.Text;
5 | using Unity.MPE;
6 | using UnityEditor;
7 | using UnityEngine;
8 |
9 | public static class ChannelCommunicationDocExample
10 | {
11 | [MenuItem("ChannelDoc/Step 1")]
12 | static void StartChannelService()
13 | {
14 | if (!ChannelService.IsRunning())
15 | {
16 | ChannelService.Start();
17 | }
18 | Debug.Log($"[Step1] ChannelService Running: {ChannelService.GetAddress()}:{ChannelService.GetPort()}");
19 | }
20 |
21 | static int s_BinaryChannelId;
22 | static int s_StringChannelId;
23 | static Action s_DisconnectBinaryChannel;
24 | static Action s_DisconnectStringChannel;
25 |
26 | [MenuItem("ChannelDoc/Step 2")]
27 | static void SetupChannelService()
28 | {
29 | if (s_DisconnectBinaryChannel == null)
30 | {
31 | s_DisconnectBinaryChannel = ChannelService.GetOrCreateChannel("custom_binary_ping_pong", HandleChannelBinaryMessage);
32 | s_BinaryChannelId = ChannelService.ChannelNameToId("custom_binary_ping_pong");
33 | }
34 | Debug.Log($"[Step2] Setup channel_custom_binary id: {s_BinaryChannelId}");
35 |
36 | if (s_DisconnectStringChannel == null)
37 | {
38 | s_DisconnectStringChannel = ChannelService.GetOrCreateChannel("custom_ascii_ping_pong", HandleChannelStringMessage);
39 | s_StringChannelId = ChannelService.ChannelNameToId("custom_ascii_ping_pong");
40 | }
41 | Debug.Log($"[Step2] Setup channel_custom_ascii id: {s_StringChannelId}");
42 | }
43 |
44 | static void HandleChannelBinaryMessage(int connectionId, byte[] data)
45 | {
46 | var msg = "";
47 | for (var i = 0; i < Math.Min(10, data.Length); ++i)
48 | {
49 | msg += data[i].ToString();
50 | }
51 | Debug.Log($"Channel Handling binary from connection {connectionId} - {data.Length} bytes - {msg}");
52 |
53 | // Let's pong it back:
54 | ChannelService.Send(connectionId, data);
55 | }
56 |
57 | static void HandleChannelStringMessage(int connectionId, byte[] data)
58 | {
59 | // We are receiving a new message. All message are always handled as bytes in a ChannelHandler.
60 | // Since our clients are expecting string data, encode the data and send it back as a string:
61 |
62 | var msgStr = Encoding.UTF8.GetString(data);
63 | Debug.Log($"Channel Handling string from connection {connectionId} - {msgStr}");
64 |
65 | // Let's pong it back:
66 | ChannelService.Send(connectionId, msgStr);
67 | }
68 |
69 | static ChannelClient s_BinaryClient;
70 | static Action s_DisconnectBinaryClient;
71 | static ChannelClient s_StringClient;
72 | static Action s_DisconnectStringClient;
73 | [MenuItem("ChannelDoc/Step 3")]
74 | static void SetupChannelClient()
75 | {
76 | const bool autoTick = true;
77 |
78 | if (s_BinaryClient == null)
79 | {
80 | s_BinaryClient = ChannelClient.GetOrCreateClient("custom_binary_ping_pong");
81 | s_BinaryClient.Start(autoTick);
82 | s_DisconnectBinaryClient = s_BinaryClient.On(HandleClientBinaryMessage);
83 | }
84 | Debug.Log($"[Step3] Setup client for channel custom_binary_ping_pong. ClientId: {s_BinaryClient.clientId}");
85 |
86 | if (s_StringClient == null)
87 | {
88 | s_StringClient = ChannelClient.GetOrCreateClient("custom_ascii_ping_pong");
89 | s_StringClient.Start(autoTick);
90 | s_DisconnectStringClient = s_StringClient.On(HandleClientStringMessage);
91 | }
92 | Debug.Log($"[Step3] Setup client for channel custom_ascii_ping_pong. ClientId: {s_StringClient.clientId}");
93 | }
94 |
95 | static void HandleClientBinaryMessage(byte[] data)
96 | {
97 | Debug.Log($"Receiving pong binary data: {data} for clientId: {s_BinaryClient.clientId} with channelName: {s_BinaryClient.channelName}");
98 | }
99 |
100 | static void HandleClientStringMessage(string data)
101 | {
102 | Debug.Log($"Receiving pong data: {data} for clientId: {s_StringClient.clientId} with channelName: {s_StringClient.channelName}");
103 | }
104 |
105 | [MenuItem("ChannelDoc/Step 4")]
106 | static void ClientSendMessageToServer()
107 | {
108 | Debug.Log("[Step 4]: Clients are sending data!");
109 | s_BinaryClient.Send(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 });
110 | s_StringClient.Send("Hello world!");
111 | }
112 |
113 | [MenuItem("ChannelDoc/Step 5")]
114 | static void CloseClients()
115 | {
116 | Debug.Log("[Step 5]: Closing clients");
117 | s_DisconnectBinaryClient();
118 | s_BinaryClient.Close();
119 |
120 | s_DisconnectStringClient();
121 | s_StringClient.Close();
122 | }
123 |
124 | [MenuItem("ChannelDoc/Step 6")]
125 | static void CloseService()
126 | {
127 | Debug.Log("[Step 6]: Closing clients");
128 |
129 | s_DisconnectBinaryChannel();
130 | s_DisconnectStringChannel();
131 |
132 | ChannelService.Stop();
133 | }
134 | }
135 |
136 | /*
137 | If you execute the 6 menu item one after the other, this will print the following
138 | in the console:
139 |
140 | [Step1] ChannelService Running: 127.0.0.1:64647
141 |
142 | [Step2] Setup channel_custom_binary id: -1698345965
143 |
144 | [Step2] Setup channel_custom_ascii id: -930064725
145 |
146 | [Step3] Setup client for channel custom_binary_ping_pong. ClientId: -1698345965
147 |
148 | [Step3] Setup client for channel custom_ascii_ping_pong. ClientId: -930064725
149 |
150 | [Step 4]: Clients are sending data!
151 |
152 | Channel Handling binary from connection 1 - 8 bytes - 01234567
153 |
154 | Channel Handling string from connection 2 - Hello world!
155 |
156 | Receiving pong binary data: System.Byte[] for clientId: -1698345965 with channelName: custom_binary_ping_pong
157 |
158 | Receiving pong data: Hello world! for clientId: -930064725 with channelName: custom_ascii_ping_pong
159 |
160 | [Step 5]: Closing clients
161 |
162 | [Step 6]: Closing clients
163 |
164 | */
165 | #endif
--------------------------------------------------------------------------------
/Editor/ChannelServiceAPI.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Text;
8 | using System.Xml.Schema;
9 | using Unity.MPE;
10 | using UnityEditor;
11 | using UnityEngine;
12 |
13 | public static class ChannelServiceAPI
14 | {
15 | static Type[] s_AllTypes;
16 | static Type s_ChannelServiceType;
17 | static Type s_ChannelInfoType;
18 | static Type s_ChannelHandlerType;
19 |
20 | public delegate void ChannelHandler(int clientId, byte[] binaryData);
21 |
22 | static ChannelServiceAPI()
23 | {
24 | var assembly = typeof(EventService).Assembly;
25 | s_AllTypes = assembly.GetTypes().ToArray();
26 | s_ChannelServiceType = s_AllTypes.FirstOrDefault(t => t.Name == "ChannelService");
27 | s_ChannelInfoType = s_AllTypes.FirstOrDefault(t => t.Name == "ChannelInfo");
28 | s_ChannelHandlerType = s_AllTypes.FirstOrDefault(t => t.Name == "ChannelHandler");
29 |
30 | EditorApplication.quitting += () =>
31 | {
32 | var infoFile = GetIsConnectedFile();
33 | if (File.Exists(infoFile))
34 | {
35 | File.Delete(infoFile);
36 | }
37 | };
38 | }
39 |
40 | public static void StartChannelService()
41 | {
42 | Debug.Log("Starting ChannelService");
43 | var startFunction = s_ChannelServiceType.GetMethod("Start", BindingFlags.Public | BindingFlags.Static);
44 | startFunction.Invoke(null, new object[0]);
45 |
46 | if (IsRunning())
47 | {
48 | var infoFile = GetIsConnectedFile();
49 | if (File.Exists(infoFile))
50 | {
51 | File.Delete(infoFile);
52 | }
53 | File.WriteAllText(infoFile, $"{GetAddress()}:{GetPort()}");
54 | }
55 | }
56 |
57 | public static void CloseChannel(string channelName)
58 | {
59 | Debug.Log($"Closing Channel {channelName}");
60 | var startFunction = s_ChannelServiceType.GetMethod("Start", BindingFlags.Public | BindingFlags.Static);
61 | startFunction.Invoke(null, new object[0]);
62 | }
63 |
64 | public static void StopChannelService()
65 | {
66 | Debug.Log("Stopping ChannelService");
67 | var stopFunction = s_ChannelServiceType.GetMethod("Stop", BindingFlags.Public | BindingFlags.Static);
68 | stopFunction.Invoke(null, new object[0]);
69 | }
70 |
71 | public static int GetChannelId(string channelName)
72 | {
73 | var getChannelInfoFunction = s_ChannelServiceType.GetMethod("GetChannelFromName", BindingFlags.NonPublic | BindingFlags.Static);
74 | var channelInfo = getChannelInfoFunction.Invoke(null, new [] { channelName });
75 | var channelIdProperty = s_ChannelInfoType.GetProperty("channelId", BindingFlags.Public | BindingFlags.Instance);
76 | return (int)channelIdProperty.GetValue(channelInfo);
77 | }
78 |
79 | public static string GetAddress()
80 | {
81 | var getAddressFunction = s_ChannelServiceType.GetMethod("GetAddress", BindingFlags.Public | BindingFlags.Static);
82 | return getAddressFunction.Invoke(null, new object[0]) as string;
83 | }
84 |
85 | public static bool IsRunning()
86 | {
87 | var isRunningFunction = s_ChannelServiceType.GetMethod("IsRunning", BindingFlags.Public | BindingFlags.Static);
88 | return (bool)isRunningFunction.Invoke(null, new object[0]);
89 | }
90 |
91 | public static int GetPort()
92 | {
93 | var getPortFunction = s_ChannelServiceType.GetMethod("GetPort", BindingFlags.Public | BindingFlags.Static);
94 | return (int)getPortFunction.Invoke(null, new object[0]);
95 | }
96 |
97 | internal static Action GetOrCreateChannel(string channelName, ChannelHandler handler)
98 | {
99 | Debug.Log($"Channel {channelName} ready");
100 | var getOrCreateChannelFunction = s_ChannelServiceType.GetMethod("GetOrCreateChannel", BindingFlags.Public | BindingFlags.Static);
101 | var properTypeHandler = ConvertDelegate(handler, s_ChannelHandlerType);
102 | return getOrCreateChannelFunction.Invoke(null, new object[] { channelName, properTypeHandler }) as Action;
103 | }
104 |
105 | // Broadcast to all connections on the same channel
106 | public static void BroadcastBinary(int channelId, byte[] data)
107 | {
108 | var broadcastFunction = s_ChannelServiceType.GetMethod("BroadcastBinary", BindingFlags.NonPublic | BindingFlags.Static);
109 | broadcastFunction.Invoke(null, new object[] { channelId, data });
110 | }
111 |
112 | // Send direct message to specific connection
113 | public static void SendBinary(int connectionId, byte[] data)
114 | {
115 | var sendFunction = s_ChannelServiceType.GetMethod("SendBinary", BindingFlags.NonPublic | BindingFlags.Static);
116 | sendFunction.Invoke(null, new object[] { connectionId, data });
117 | }
118 |
119 | public static void Broadcast(int channelId, byte[] data)
120 | {
121 | var broadcastFunction = s_ChannelServiceType.GetMethods(BindingFlags.Public | BindingFlags.Static).First(mi => mi.Name == "Broadcast" && mi.GetParameters()[1].ParameterType == typeof(string));
122 | broadcastFunction.Invoke(null, new object[] { channelId, data });
123 | }
124 |
125 | // Send direct message to specific connection
126 | public static void Send(int connectionId, string data)
127 | {
128 | var sendFunction = s_ChannelServiceType.GetMethods(BindingFlags.Public | BindingFlags.Static).First(mi => mi.Name == "Send" && mi.GetParameters()[1].ParameterType == typeof(string));
129 | sendFunction.Invoke(null, new object[] { connectionId, data });
130 | }
131 |
132 | static Delegate ConvertDelegate(Delegate sourceDelegate, Type targetType)
133 | {
134 | return Delegate.CreateDelegate(
135 | targetType,
136 | sourceDelegate.Target,
137 | sourceDelegate.Method);
138 | }
139 |
140 | static string GetIsConnectedFile()
141 | {
142 | return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Unity", "Editor", "ChannelService.info").Replace("\\", "/");
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/extension.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //
4 | // Allowed token characters:
5 | //
6 | // '!', '#', '$', '%', '&', ''', '*', '+', '-',
7 | // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
8 | //
9 | // tokenChars[32] === 0 // ' '
10 | // tokenChars[33] === 1 // '!'
11 | // tokenChars[34] === 0 // '"'
12 | // ...
13 | //
14 | const tokenChars = [
15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
17 | 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
18 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
19 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
20 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
21 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
22 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
23 | ];
24 |
25 | /**
26 | * Adds an offer to the map of extension offers or a parameter to the map of
27 | * parameters.
28 | *
29 | * @param {Object} dest The map of extension offers or parameters
30 | * @param {String} name The extension or parameter name
31 | * @param {(Object|Boolean|String)} elem The extension parameters or the
32 | * parameter value
33 | * @private
34 | */
35 | function push (dest, name, elem) {
36 | if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
37 | else dest[name] = [elem];
38 | }
39 |
40 | /**
41 | * Parses the `Sec-WebSocket-Extensions` header into an object.
42 | *
43 | * @param {String} header The field value of the header
44 | * @return {Object} The parsed object
45 | * @public
46 | */
47 | function parse (header) {
48 | const offers = {};
49 |
50 | if (header === undefined || header === '') return offers;
51 |
52 | var params = {};
53 | var mustUnescape = false;
54 | var isEscaping = false;
55 | var inQuotes = false;
56 | var extensionName;
57 | var paramName;
58 | var start = -1;
59 | var end = -1;
60 |
61 | for (var i = 0; i < header.length; i++) {
62 | const code = header.charCodeAt(i);
63 |
64 | if (extensionName === undefined) {
65 | if (end === -1 && tokenChars[code] === 1) {
66 | if (start === -1) start = i;
67 | } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) {
68 | if (end === -1 && start !== -1) end = i;
69 | } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) {
70 | if (start === -1) {
71 | throw new SyntaxError(`Unexpected character at index ${i}`);
72 | }
73 |
74 | if (end === -1) end = i;
75 | const name = header.slice(start, end);
76 | if (code === 0x2c) {
77 | push(offers, name, params);
78 | params = {};
79 | } else {
80 | extensionName = name;
81 | }
82 |
83 | start = end = -1;
84 | } else {
85 | throw new SyntaxError(`Unexpected character at index ${i}`);
86 | }
87 | } else if (paramName === undefined) {
88 | if (end === -1 && tokenChars[code] === 1) {
89 | if (start === -1) start = i;
90 | } else if (code === 0x20 || code === 0x09) {
91 | if (end === -1 && start !== -1) end = i;
92 | } else if (code === 0x3b || code === 0x2c) {
93 | if (start === -1) {
94 | throw new SyntaxError(`Unexpected character at index ${i}`);
95 | }
96 |
97 | if (end === -1) end = i;
98 | push(params, header.slice(start, end), true);
99 | if (code === 0x2c) {
100 | push(offers, extensionName, params);
101 | params = {};
102 | extensionName = undefined;
103 | }
104 |
105 | start = end = -1;
106 | } else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) {
107 | paramName = header.slice(start, i);
108 | start = end = -1;
109 | } else {
110 | throw new SyntaxError(`Unexpected character at index ${i}`);
111 | }
112 | } else {
113 | //
114 | // The value of a quoted-string after unescaping must conform to the
115 | // token ABNF, so only token characters are valid.
116 | // Ref: https://tools.ietf.org/html/rfc6455#section-9.1
117 | //
118 | if (isEscaping) {
119 | if (tokenChars[code] !== 1) {
120 | throw new SyntaxError(`Unexpected character at index ${i}`);
121 | }
122 | if (start === -1) start = i;
123 | else if (!mustUnescape) mustUnescape = true;
124 | isEscaping = false;
125 | } else if (inQuotes) {
126 | if (tokenChars[code] === 1) {
127 | if (start === -1) start = i;
128 | } else if (code === 0x22/* '"' */ && start !== -1) {
129 | inQuotes = false;
130 | end = i;
131 | } else if (code === 0x5c/* '\' */) {
132 | isEscaping = true;
133 | } else {
134 | throw new SyntaxError(`Unexpected character at index ${i}`);
135 | }
136 | } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
137 | inQuotes = true;
138 | } else if (end === -1 && tokenChars[code] === 1) {
139 | if (start === -1) start = i;
140 | } else if (start !== -1 && (code === 0x20 || code === 0x09)) {
141 | if (end === -1) end = i;
142 | } else if (code === 0x3b || code === 0x2c) {
143 | if (start === -1) {
144 | throw new SyntaxError(`Unexpected character at index ${i}`);
145 | }
146 |
147 | if (end === -1) end = i;
148 | var value = header.slice(start, end);
149 | if (mustUnescape) {
150 | value = value.replace(/\\/g, '');
151 | mustUnescape = false;
152 | }
153 | push(params, paramName, value);
154 | if (code === 0x2c) {
155 | push(offers, extensionName, params);
156 | params = {};
157 | extensionName = undefined;
158 | }
159 |
160 | paramName = undefined;
161 | start = end = -1;
162 | } else {
163 | throw new SyntaxError(`Unexpected character at index ${i}`);
164 | }
165 | }
166 | }
167 |
168 | if (start === -1 || inQuotes) {
169 | throw new SyntaxError('Unexpected end of input');
170 | }
171 |
172 | if (end === -1) end = i;
173 | const token = header.slice(start, end);
174 | if (extensionName === undefined) {
175 | push(offers, token, {});
176 | } else {
177 | if (paramName === undefined) {
178 | push(params, token, true);
179 | } else if (mustUnescape) {
180 | push(params, paramName, token.replace(/\\/g, ''));
181 | } else {
182 | push(params, paramName, token);
183 | }
184 | push(offers, extensionName, params);
185 | }
186 |
187 | return offers;
188 | }
189 |
190 | /**
191 | * Builds the `Sec-WebSocket-Extensions` header field value.
192 | *
193 | * @param {Object} extensions The map of extensions and parameters to format
194 | * @return {String} A string representing the given object
195 | * @public
196 | */
197 | function format (extensions) {
198 | return Object.keys(extensions).map((extension) => {
199 | var configurations = extensions[extension];
200 | if (!Array.isArray(configurations)) configurations = [configurations];
201 | return configurations.map((params) => {
202 | return [extension].concat(Object.keys(params).map((k) => {
203 | var values = params[k];
204 | if (!Array.isArray(values)) values = [values];
205 | return values.map((v) => v === true ? k : `${k}=${v}`).join('; ');
206 | })).join('; ');
207 | }).join(', ');
208 | }).join(', ');
209 | }
210 |
211 | module.exports = { format, parse };
212 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/websocket-server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EventEmitter = require('events');
4 | const crypto = require('crypto');
5 | const http = require('http');
6 | const url = require('url');
7 |
8 | const PerMessageDeflate = require('./permessage-deflate');
9 | const extension = require('./extension');
10 | const constants = require('./constants');
11 | const WebSocket = require('./websocket');
12 |
13 | /**
14 | * Class representing a WebSocket server.
15 | *
16 | * @extends EventEmitter
17 | */
18 | class WebSocketServer extends EventEmitter {
19 | /**
20 | * Create a `WebSocketServer` instance.
21 | *
22 | * @param {Object} options Configuration options
23 | * @param {String} options.host The hostname where to bind the server
24 | * @param {Number} options.port The port where to bind the server
25 | * @param {http.Server} options.server A pre-created HTTP/S server to use
26 | * @param {Function} options.verifyClient An hook to reject connections
27 | * @param {Function} options.handleProtocols An hook to handle protocols
28 | * @param {String} options.path Accept only connections matching this path
29 | * @param {Boolean} options.noServer Enable no server mode
30 | * @param {Boolean} options.clientTracking Specifies whether or not to track clients
31 | * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
32 | * @param {Number} options.maxPayload The maximum allowed message size
33 | * @param {Function} callback A listener for the `listening` event
34 | */
35 | constructor (options, callback) {
36 | super();
37 |
38 | options = Object.assign({
39 | maxPayload: 100 * 1024 * 1024,
40 | perMessageDeflate: false,
41 | handleProtocols: null,
42 | clientTracking: true,
43 | verifyClient: null,
44 | noServer: false,
45 | backlog: null, // use default (511 as implemented in net.js)
46 | server: null,
47 | host: null,
48 | path: null,
49 | port: null
50 | }, options);
51 |
52 | if (options.port == null && !options.server && !options.noServer) {
53 | throw new TypeError(
54 | 'One of the "port", "server", or "noServer" options must be specified'
55 | );
56 | }
57 |
58 | if (options.port != null) {
59 | this._server = http.createServer((req, res) => {
60 | const body = http.STATUS_CODES[426];
61 |
62 | res.writeHead(426, {
63 | 'Content-Length': body.length,
64 | 'Content-Type': 'text/plain'
65 | });
66 | res.end(body);
67 | });
68 | this._server.listen(options.port, options.host, options.backlog, callback);
69 | } else if (options.server) {
70 | this._server = options.server;
71 | }
72 |
73 | if (this._server) {
74 | this._removeListeners = addListeners(this._server, {
75 | listening: this.emit.bind(this, 'listening'),
76 | error: this.emit.bind(this, 'error'),
77 | upgrade: (req, socket, head) => {
78 | this.handleUpgrade(req, socket, head, (ws) => {
79 | this.emit('connection', ws, req);
80 | });
81 | }
82 | });
83 | }
84 |
85 | if (options.perMessageDeflate === true) options.perMessageDeflate = {};
86 | if (options.clientTracking) this.clients = new Set();
87 | this.options = options;
88 | }
89 |
90 | /**
91 | * Returns the bound address, the address family name, and port of the server
92 | * as reported by the operating system if listening on an IP socket.
93 | * If the server is listening on a pipe or UNIX domain socket, the name is
94 | * returned as a string.
95 | *
96 | * @return {(Object|String|null)} The address of the server
97 | * @public
98 | */
99 | address () {
100 | if (this.options.noServer) {
101 | throw new Error('The server is operating in "noServer" mode');
102 | }
103 |
104 | if (!this._server) return null;
105 | return this._server.address();
106 | }
107 |
108 | /**
109 | * Close the server.
110 | *
111 | * @param {Function} cb Callback
112 | * @public
113 | */
114 | close (cb) {
115 | //
116 | // Terminate all associated clients.
117 | //
118 | if (this.clients) {
119 | for (const client of this.clients) client.terminate();
120 | }
121 |
122 | const server = this._server;
123 |
124 | if (server) {
125 | this._removeListeners();
126 | this._removeListeners = this._server = null;
127 |
128 | //
129 | // Close the http server if it was internally created.
130 | //
131 | if (this.options.port != null) return server.close(cb);
132 | }
133 |
134 | if (cb) cb();
135 | }
136 |
137 | /**
138 | * See if a given request should be handled by this server instance.
139 | *
140 | * @param {http.IncomingMessage} req Request object to inspect
141 | * @return {Boolean} `true` if the request is valid, else `false`
142 | * @public
143 | */
144 | shouldHandle (req) {
145 | if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
146 | return false;
147 | }
148 |
149 | return true;
150 | }
151 |
152 | /**
153 | * Handle a HTTP Upgrade request.
154 | *
155 | * @param {http.IncomingMessage} req The request object
156 | * @param {net.Socket} socket The network socket between the server and client
157 | * @param {Buffer} head The first packet of the upgraded stream
158 | * @param {Function} cb Callback
159 | * @public
160 | */
161 | handleUpgrade (req, socket, head, cb) {
162 | socket.on('error', socketOnError);
163 |
164 | const version = +req.headers['sec-websocket-version'];
165 | const extensions = {};
166 |
167 | if (
168 | req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
169 | !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
170 | !this.shouldHandle(req)
171 | ) {
172 | return abortHandshake(socket, 400);
173 | }
174 |
175 | if (this.options.perMessageDeflate) {
176 | const perMessageDeflate = new PerMessageDeflate(
177 | this.options.perMessageDeflate,
178 | true,
179 | this.options.maxPayload
180 | );
181 |
182 | try {
183 | const offers = extension.parse(
184 | req.headers['sec-websocket-extensions']
185 | );
186 |
187 | if (offers[PerMessageDeflate.extensionName]) {
188 | perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
189 | extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
190 | }
191 | } catch (err) {
192 | return abortHandshake(socket, 400);
193 | }
194 | }
195 |
196 | //
197 | // Optionally call external client verification handler.
198 | //
199 | if (this.options.verifyClient) {
200 | const info = {
201 | origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
202 | secure: !!(req.connection.authorized || req.connection.encrypted),
203 | req
204 | };
205 |
206 | if (this.options.verifyClient.length === 2) {
207 | this.options.verifyClient(info, (verified, code, message, headers) => {
208 | if (!verified) {
209 | return abortHandshake(socket, code || 401, message, headers);
210 | }
211 |
212 | this.completeUpgrade(extensions, req, socket, head, cb);
213 | });
214 | return;
215 | }
216 |
217 | if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
218 | }
219 |
220 | this.completeUpgrade(extensions, req, socket, head, cb);
221 | }
222 |
223 | /**
224 | * Upgrade the connection to WebSocket.
225 | *
226 | * @param {Object} extensions The accepted extensions
227 | * @param {http.IncomingMessage} req The request object
228 | * @param {net.Socket} socket The network socket between the server and client
229 | * @param {Buffer} head The first packet of the upgraded stream
230 | * @param {Function} cb Callback
231 | * @private
232 | */
233 | completeUpgrade (extensions, req, socket, head, cb) {
234 | //
235 | // Destroy the socket if the client has already sent a FIN packet.
236 | //
237 | if (!socket.readable || !socket.writable) return socket.destroy();
238 |
239 | const key = crypto.createHash('sha1')
240 | .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
241 | .digest('base64');
242 |
243 | const headers = [
244 | 'HTTP/1.1 101 Switching Protocols',
245 | 'Upgrade: websocket',
246 | 'Connection: Upgrade',
247 | `Sec-WebSocket-Accept: ${key}`
248 | ];
249 |
250 | const ws = new WebSocket(null);
251 | var protocol = req.headers['sec-websocket-protocol'];
252 |
253 | if (protocol) {
254 | protocol = protocol.trim().split(/ *, */);
255 |
256 | //
257 | // Optionally call external protocol selection handler.
258 | //
259 | if (this.options.handleProtocols) {
260 | protocol = this.options.handleProtocols(protocol, req);
261 | } else {
262 | protocol = protocol[0];
263 | }
264 |
265 | if (protocol) {
266 | headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
267 | ws.protocol = protocol;
268 | }
269 | }
270 |
271 | if (extensions[PerMessageDeflate.extensionName]) {
272 | const params = extensions[PerMessageDeflate.extensionName].params;
273 | const value = extension.format({
274 | [PerMessageDeflate.extensionName]: [params]
275 | });
276 | headers.push(`Sec-WebSocket-Extensions: ${value}`);
277 | ws._extensions = extensions;
278 | }
279 |
280 | //
281 | // Allow external modification/inspection of handshake headers.
282 | //
283 | this.emit('headers', headers, req);
284 |
285 | socket.write(headers.concat('\r\n').join('\r\n'));
286 | socket.removeListener('error', socketOnError);
287 |
288 | ws.setSocket(socket, head, this.options.maxPayload);
289 |
290 | if (this.clients) {
291 | this.clients.add(ws);
292 | ws.on('close', () => this.clients.delete(ws));
293 | }
294 |
295 | cb(ws);
296 | }
297 | }
298 |
299 | module.exports = WebSocketServer;
300 |
301 | /**
302 | * Add event listeners on an `EventEmitter` using a map of
303 | * pairs.
304 | *
305 | * @param {EventEmitter} server The event emitter
306 | * @param {Object.} map The listeners to add
307 | * @return {Function} A function that will remove the added listeners when called
308 | * @private
309 | */
310 | function addListeners (server, map) {
311 | for (const event of Object.keys(map)) server.on(event, map[event]);
312 |
313 | return function removeListeners () {
314 | for (const event of Object.keys(map)) {
315 | server.removeListener(event, map[event]);
316 | }
317 | };
318 | }
319 |
320 | /**
321 | * Handle premature socket errors.
322 | *
323 | * @private
324 | */
325 | function socketOnError () {
326 | this.destroy();
327 | }
328 |
329 | /**
330 | * Close the connection when preconditions are not fulfilled.
331 | *
332 | * @param {net.Socket} socket The socket of the upgrade request
333 | * @param {Number} code The HTTP response status code
334 | * @param {String} [message] The HTTP response body
335 | * @param {Object} [headers] Additional HTTP response headers
336 | * @private
337 | */
338 | function abortHandshake (socket, code, message, headers) {
339 | if (socket.writable) {
340 | message = message || http.STATUS_CODES[code];
341 | headers = Object.assign({
342 | 'Connection': 'close',
343 | 'Content-type': 'text/html',
344 | 'Content-Length': Buffer.byteLength(message)
345 | }, headers);
346 |
347 | socket.write(
348 | `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
349 | Object.keys(headers).map(h => `${h}: ${headers[h]}`).join('\r\n') +
350 | '\r\n\r\n' +
351 | message
352 | );
353 | }
354 |
355 | socket.removeListener('error', socketOnError);
356 | socket.destroy();
357 | }
358 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/sender.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const crypto = require('crypto');
4 |
5 | const PerMessageDeflate = require('./permessage-deflate');
6 | const bufferUtil = require('./buffer-util');
7 | const validation = require('./validation');
8 | const constants = require('./constants');
9 |
10 | /**
11 | * HyBi Sender implementation.
12 | */
13 | class Sender {
14 | /**
15 | * Creates a Sender instance.
16 | *
17 | * @param {net.Socket} socket The connection socket
18 | * @param {Object} extensions An object containing the negotiated extensions
19 | */
20 | constructor (socket, extensions) {
21 | this._extensions = extensions || {};
22 | this._socket = socket;
23 |
24 | this._firstFragment = true;
25 | this._compress = false;
26 |
27 | this._bufferedBytes = 0;
28 | this._deflating = false;
29 | this._queue = [];
30 | }
31 |
32 | /**
33 | * Frames a piece of data according to the HyBi WebSocket protocol.
34 | *
35 | * @param {Buffer} data The data to frame
36 | * @param {Object} options Options object
37 | * @param {Number} options.opcode The opcode
38 | * @param {Boolean} options.readOnly Specifies whether `data` can be modified
39 | * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
40 | * @param {Boolean} options.mask Specifies whether or not to mask `data`
41 | * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
42 | * @return {Buffer[]} The framed data as a list of `Buffer` instances
43 | * @public
44 | */
45 | static frame (data, options) {
46 | const merge = data.length < 1024 || (options.mask && options.readOnly);
47 | var offset = options.mask ? 6 : 2;
48 | var payloadLength = data.length;
49 |
50 | if (data.length >= 65536) {
51 | offset += 8;
52 | payloadLength = 127;
53 | } else if (data.length > 125) {
54 | offset += 2;
55 | payloadLength = 126;
56 | }
57 |
58 | const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
59 |
60 | target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
61 | if (options.rsv1) target[0] |= 0x40;
62 |
63 | if (payloadLength === 126) {
64 | target.writeUInt16BE(data.length, 2);
65 | } else if (payloadLength === 127) {
66 | target.writeUInt32BE(0, 2);
67 | target.writeUInt32BE(data.length, 6);
68 | }
69 |
70 | if (!options.mask) {
71 | target[1] = payloadLength;
72 | if (merge) {
73 | data.copy(target, offset);
74 | return [target];
75 | }
76 |
77 | return [target, data];
78 | }
79 |
80 | const mask = crypto.randomBytes(4);
81 |
82 | target[1] = payloadLength | 0x80;
83 | target[offset - 4] = mask[0];
84 | target[offset - 3] = mask[1];
85 | target[offset - 2] = mask[2];
86 | target[offset - 1] = mask[3];
87 |
88 | if (merge) {
89 | bufferUtil.mask(data, mask, target, offset, data.length);
90 | return [target];
91 | }
92 |
93 | bufferUtil.mask(data, mask, data, 0, data.length);
94 | return [target, data];
95 | }
96 |
97 | /**
98 | * Sends a close message to the other peer.
99 | *
100 | * @param {(Number|undefined)} code The status code component of the body
101 | * @param {String} data The message component of the body
102 | * @param {Boolean} mask Specifies whether or not to mask the message
103 | * @param {Function} cb Callback
104 | * @public
105 | */
106 | close (code, data, mask, cb) {
107 | var buf;
108 |
109 | if (code === undefined) {
110 | buf = constants.EMPTY_BUFFER;
111 | } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) {
112 | throw new TypeError('First argument must be a valid error code number');
113 | } else if (data === undefined || data === '') {
114 | buf = Buffer.allocUnsafe(2);
115 | buf.writeUInt16BE(code, 0);
116 | } else {
117 | buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
118 | buf.writeUInt16BE(code, 0);
119 | buf.write(data, 2);
120 | }
121 |
122 | if (this._deflating) {
123 | this.enqueue([this.doClose, buf, mask, cb]);
124 | } else {
125 | this.doClose(buf, mask, cb);
126 | }
127 | }
128 |
129 | /**
130 | * Frames and sends a close message.
131 | *
132 | * @param {Buffer} data The message to send
133 | * @param {Boolean} mask Specifies whether or not to mask `data`
134 | * @param {Function} cb Callback
135 | * @private
136 | */
137 | doClose (data, mask, cb) {
138 | this.sendFrame(Sender.frame(data, {
139 | fin: true,
140 | rsv1: false,
141 | opcode: 0x08,
142 | mask,
143 | readOnly: false
144 | }), cb);
145 | }
146 |
147 | /**
148 | * Sends a ping message to the other peer.
149 | *
150 | * @param {*} data The message to send
151 | * @param {Boolean} mask Specifies whether or not to mask `data`
152 | * @param {Function} cb Callback
153 | * @public
154 | */
155 | ping (data, mask, cb) {
156 | var readOnly = true;
157 |
158 | if (!Buffer.isBuffer(data)) {
159 | if (data instanceof ArrayBuffer) {
160 | data = Buffer.from(data);
161 | } else if (ArrayBuffer.isView(data)) {
162 | data = viewToBuffer(data);
163 | } else {
164 | data = Buffer.from(data);
165 | readOnly = false;
166 | }
167 | }
168 |
169 | if (this._deflating) {
170 | this.enqueue([this.doPing, data, mask, readOnly, cb]);
171 | } else {
172 | this.doPing(data, mask, readOnly, cb);
173 | }
174 | }
175 |
176 | /**
177 | * Frames and sends a ping message.
178 | *
179 | * @param {*} data The message to send
180 | * @param {Boolean} mask Specifies whether or not to mask `data`
181 | * @param {Boolean} readOnly Specifies whether `data` can be modified
182 | * @param {Function} cb Callback
183 | * @private
184 | */
185 | doPing (data, mask, readOnly, cb) {
186 | this.sendFrame(Sender.frame(data, {
187 | fin: true,
188 | rsv1: false,
189 | opcode: 0x09,
190 | mask,
191 | readOnly
192 | }), cb);
193 | }
194 |
195 | /**
196 | * Sends a pong message to the other peer.
197 | *
198 | * @param {*} data The message to send
199 | * @param {Boolean} mask Specifies whether or not to mask `data`
200 | * @param {Function} cb Callback
201 | * @public
202 | */
203 | pong (data, mask, cb) {
204 | var readOnly = true;
205 |
206 | if (!Buffer.isBuffer(data)) {
207 | if (data instanceof ArrayBuffer) {
208 | data = Buffer.from(data);
209 | } else if (ArrayBuffer.isView(data)) {
210 | data = viewToBuffer(data);
211 | } else {
212 | data = Buffer.from(data);
213 | readOnly = false;
214 | }
215 | }
216 |
217 | if (this._deflating) {
218 | this.enqueue([this.doPong, data, mask, readOnly, cb]);
219 | } else {
220 | this.doPong(data, mask, readOnly, cb);
221 | }
222 | }
223 |
224 | /**
225 | * Frames and sends a pong message.
226 | *
227 | * @param {*} data The message to send
228 | * @param {Boolean} mask Specifies whether or not to mask `data`
229 | * @param {Boolean} readOnly Specifies whether `data` can be modified
230 | * @param {Function} cb Callback
231 | * @private
232 | */
233 | doPong (data, mask, readOnly, cb) {
234 | this.sendFrame(Sender.frame(data, {
235 | fin: true,
236 | rsv1: false,
237 | opcode: 0x0a,
238 | mask,
239 | readOnly
240 | }), cb);
241 | }
242 |
243 | /**
244 | * Sends a data message to the other peer.
245 | *
246 | * @param {*} data The message to send
247 | * @param {Object} options Options object
248 | * @param {Boolean} options.compress Specifies whether or not to compress `data`
249 | * @param {Boolean} options.binary Specifies whether `data` is binary or text
250 | * @param {Boolean} options.fin Specifies whether the fragment is the last one
251 | * @param {Boolean} options.mask Specifies whether or not to mask `data`
252 | * @param {Function} cb Callback
253 | * @public
254 | */
255 | send (data, options, cb) {
256 | var opcode = options.binary ? 2 : 1;
257 | var rsv1 = options.compress;
258 | var readOnly = true;
259 |
260 | if (!Buffer.isBuffer(data)) {
261 | if (data instanceof ArrayBuffer) {
262 | data = Buffer.from(data);
263 | } else if (ArrayBuffer.isView(data)) {
264 | data = viewToBuffer(data);
265 | } else {
266 | data = Buffer.from(data);
267 | readOnly = false;
268 | }
269 | }
270 |
271 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
272 |
273 | if (this._firstFragment) {
274 | this._firstFragment = false;
275 | if (rsv1 && perMessageDeflate) {
276 | rsv1 = data.length >= perMessageDeflate._threshold;
277 | }
278 | this._compress = rsv1;
279 | } else {
280 | rsv1 = false;
281 | opcode = 0;
282 | }
283 |
284 | if (options.fin) this._firstFragment = true;
285 |
286 | if (perMessageDeflate) {
287 | const opts = {
288 | fin: options.fin,
289 | rsv1,
290 | opcode,
291 | mask: options.mask,
292 | readOnly
293 | };
294 |
295 | if (this._deflating) {
296 | this.enqueue([this.dispatch, data, this._compress, opts, cb]);
297 | } else {
298 | this.dispatch(data, this._compress, opts, cb);
299 | }
300 | } else {
301 | this.sendFrame(Sender.frame(data, {
302 | fin: options.fin,
303 | rsv1: false,
304 | opcode,
305 | mask: options.mask,
306 | readOnly
307 | }), cb);
308 | }
309 | }
310 |
311 | /**
312 | * Dispatches a data message.
313 | *
314 | * @param {Buffer} data The message to send
315 | * @param {Boolean} compress Specifies whether or not to compress `data`
316 | * @param {Object} options Options object
317 | * @param {Number} options.opcode The opcode
318 | * @param {Boolean} options.readOnly Specifies whether `data` can be modified
319 | * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
320 | * @param {Boolean} options.mask Specifies whether or not to mask `data`
321 | * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
322 | * @param {Function} cb Callback
323 | * @private
324 | */
325 | dispatch (data, compress, options, cb) {
326 | if (!compress) {
327 | this.sendFrame(Sender.frame(data, options), cb);
328 | return;
329 | }
330 |
331 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
332 |
333 | this._deflating = true;
334 | perMessageDeflate.compress(data, options.fin, (_, buf) => {
335 | options.readOnly = false;
336 | this.sendFrame(Sender.frame(buf, options), cb);
337 | this._deflating = false;
338 | this.dequeue();
339 | });
340 | }
341 |
342 | /**
343 | * Executes queued send operations.
344 | *
345 | * @private
346 | */
347 | dequeue () {
348 | while (!this._deflating && this._queue.length) {
349 | const params = this._queue.shift();
350 |
351 | this._bufferedBytes -= params[1].length;
352 | params[0].apply(this, params.slice(1));
353 | }
354 | }
355 |
356 | /**
357 | * Enqueues a send operation.
358 | *
359 | * @param {Array} params Send operation parameters.
360 | * @private
361 | */
362 | enqueue (params) {
363 | this._bufferedBytes += params[1].length;
364 | this._queue.push(params);
365 | }
366 |
367 | /**
368 | * Sends a frame.
369 | *
370 | * @param {Buffer[]} list The frame to send
371 | * @param {Function} cb Callback
372 | * @private
373 | */
374 | sendFrame (list, cb) {
375 | if (list.length === 2) {
376 | this._socket.write(list[0]);
377 | this._socket.write(list[1], cb);
378 | } else {
379 | this._socket.write(list[0], cb);
380 | }
381 | }
382 | }
383 |
384 | module.exports = Sender;
385 |
386 | /**
387 | * Converts an `ArrayBuffer` view into a buffer.
388 | *
389 | * @param {(DataView|TypedArray)} view The view to convert
390 | * @return {Buffer} Converted view
391 | * @private
392 | */
393 | function viewToBuffer (view) {
394 | const buf = Buffer.from(view.buffer);
395 |
396 | if (view.byteLength !== view.buffer.byteLength) {
397 | return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
398 | }
399 |
400 | return buf;
401 | }
402 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/README.md:
--------------------------------------------------------------------------------
1 | # ws: a Node.js WebSocket library
2 |
3 | [](https://www.npmjs.com/package/ws)
4 | [](https://travis-ci.org/websockets/ws)
5 | [](https://ci.appveyor.com/project/lpinca/ws)
6 | [](https://coveralls.io/r/websockets/ws?branch=master)
7 |
8 | ws is a simple to use, blazing fast, and thoroughly tested WebSocket client
9 | and server implementation.
10 |
11 | Passes the quite extensive Autobahn test suite: [server][server-report],
12 | [client][client-report].
13 |
14 | **Note**: This module does not work in the browser. The client in the docs is a
15 | reference to a back end with the role of a client in the WebSocket
16 | communication. Browser clients must use the native
17 | [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
18 | To make the same code work seamlessly on Node.js and the browser, you can use
19 | one of the many wrappers available on npm, like
20 | [isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).
21 |
22 | ## Table of Contents
23 |
24 | * [Protocol support](#protocol-support)
25 | * [Installing](#installing)
26 | + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance)
27 | * [API docs](#api-docs)
28 | * [WebSocket compression](#websocket-compression)
29 | * [Usage examples](#usage-examples)
30 | + [Sending and receiving text data](#sending-and-receiving-text-data)
31 | + [Sending binary data](#sending-binary-data)
32 | + [Simple server](#simple-server)
33 | + [External HTTP/S server](#external-https-server)
34 | + [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)
35 | + [Server broadcast](#server-broadcast)
36 | + [echo.websocket.org demo](#echowebsocketorg-demo)
37 | + [Other examples](#other-examples)
38 | * [Error handling best practices](#error-handling-best-practices)
39 | * [FAQ](#faq)
40 | + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
41 | + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
42 | + [How to connect via a proxy?](#how-to-connect-via-a-proxy)
43 | * [Changelog](#changelog)
44 | * [License](#license)
45 |
46 | ## Protocol support
47 |
48 | * **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
49 | * **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`)
50 |
51 | ## Installing
52 |
53 | ```
54 | npm install --save ws
55 | ```
56 |
57 | ### Opt-in for performance and spec compliance
58 |
59 | There are 2 optional modules that can be installed along side with the ws
60 | module. These modules are binary addons which improve certain operations.
61 | Prebuilt binaries are available for the most popular platforms so you don't
62 | necessarily need to have a C++ compiler installed on your machine.
63 |
64 | - `npm install --save-optional bufferutil`: Allows to efficiently perform
65 | operations such as masking and unmasking the data payload of the WebSocket
66 | frames.
67 | - `npm install --save-optional utf-8-validate`: Allows to efficiently check
68 | if a message contains valid UTF-8 as required by the spec.
69 |
70 | ## API docs
71 |
72 | See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes.
73 |
74 | ## WebSocket compression
75 |
76 | ws supports the [permessage-deflate extension][permessage-deflate] which
77 | enables the client and server to negotiate a compression algorithm and its
78 | parameters, and then selectively apply it to the data payloads of each
79 | WebSocket message.
80 |
81 | The extension is disabled by default on the server and enabled by default on
82 | the client. It adds a significant overhead in terms of performance and memory
83 | consumption so we suggest to enable it only if it is really needed.
84 |
85 | Note that Node.js has a variety of issues with high-performance compression,
86 | where increased concurrency, especially on Linux, can lead to
87 | [catastrophic memory fragmentation][node-zlib-bug] and slow performance.
88 | If you intend to use permessage-deflate in production, it is worthwhile to set
89 | up a test representative of your workload and ensure Node.js/zlib will handle
90 | it with acceptable performance and memory usage.
91 |
92 | Tuning of permessage-deflate can be done via the options defined below. You can
93 | also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly
94 | into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].
95 |
96 | See [the docs][ws-server-options] for more options.
97 |
98 | ```js
99 | const WebSocket = require('ws');
100 |
101 | const wss = new WebSocket.Server({
102 | port: 8080,
103 | perMessageDeflate: {
104 | zlibDeflateOptions: { // See zlib defaults.
105 | chunkSize: 1024,
106 | memLevel: 7,
107 | level: 3,
108 | },
109 | zlibInflateOptions: {
110 | chunkSize: 10 * 1024
111 | },
112 | // Other options settable:
113 | clientNoContextTakeover: true, // Defaults to negotiated value.
114 | serverNoContextTakeover: true, // Defaults to negotiated value.
115 | clientMaxWindowBits: 10, // Defaults to negotiated value.
116 | serverMaxWindowBits: 10, // Defaults to negotiated value.
117 | // Below options specified as default values.
118 | concurrencyLimit: 10, // Limits zlib concurrency for perf.
119 | threshold: 1024, // Size (in bytes) below which messages
120 | // should not be compressed.
121 | }
122 | });
123 | ```
124 |
125 | The client will only use the extension if it is supported and enabled on the
126 | server. To always disable the extension on the client set the
127 | `perMessageDeflate` option to `false`.
128 |
129 | ```js
130 | const WebSocket = require('ws');
131 |
132 | const ws = new WebSocket('ws://www.host.com/path', {
133 | perMessageDeflate: false
134 | });
135 | ```
136 |
137 | ## Usage examples
138 |
139 | ### Sending and receiving text data
140 |
141 | ```js
142 | const WebSocket = require('ws');
143 |
144 | const ws = new WebSocket('ws://www.host.com/path');
145 |
146 | ws.on('open', function open() {
147 | ws.send('something');
148 | });
149 |
150 | ws.on('message', function incoming(data) {
151 | console.log(data);
152 | });
153 | ```
154 |
155 | ### Sending binary data
156 |
157 | ```js
158 | const WebSocket = require('ws');
159 |
160 | const ws = new WebSocket('ws://www.host.com/path');
161 |
162 | ws.on('open', function open() {
163 | const array = new Float32Array(5);
164 |
165 | for (var i = 0; i < array.length; ++i) {
166 | array[i] = i / 2;
167 | }
168 |
169 | ws.send(array);
170 | });
171 | ```
172 |
173 | ### Simple server
174 |
175 | ```js
176 | const WebSocket = require('ws');
177 |
178 | const wss = new WebSocket.Server({ port: 8080 });
179 |
180 | wss.on('connection', function connection(ws) {
181 | ws.on('message', function incoming(message) {
182 | console.log('received: %s', message);
183 | });
184 |
185 | ws.send('something');
186 | });
187 | ```
188 |
189 | ### External HTTP/S server
190 |
191 | ```js
192 | const fs = require('fs');
193 | const https = require('https');
194 | const WebSocket = require('ws');
195 |
196 | const server = new https.createServer({
197 | cert: fs.readFileSync('/path/to/cert.pem'),
198 | key: fs.readFileSync('/path/to/key.pem')
199 | });
200 | const wss = new WebSocket.Server({ server });
201 |
202 | wss.on('connection', function connection(ws) {
203 | ws.on('message', function incoming(message) {
204 | console.log('received: %s', message);
205 | });
206 |
207 | ws.send('something');
208 | });
209 |
210 | server.listen(8080);
211 | ```
212 |
213 | ### Multiple servers sharing a single HTTP/S server
214 |
215 | ```js
216 | const http = require('http');
217 | const WebSocket = require('ws');
218 |
219 | const server = http.createServer();
220 | const wss1 = new WebSocket.Server({ noServer: true });
221 | const wss2 = new WebSocket.Server({ noServer: true });
222 |
223 | wss1.on('connection', function connection(ws) {
224 | // ...
225 | });
226 |
227 | wss2.on('connection', function connection(ws) {
228 | // ...
229 | });
230 |
231 | server.on('upgrade', function upgrade(request, socket, head) {
232 | const pathname = url.parse(request.url).pathname;
233 |
234 | if (pathname === '/foo') {
235 | wss1.handleUpgrade(request, socket, head, function done(ws) {
236 | wss1.emit('connection', ws, request);
237 | });
238 | } else if (pathname === '/bar') {
239 | wss2.handleUpgrade(request, socket, head, function done(ws) {
240 | wss2.emit('connection', ws, request);
241 | });
242 | } else {
243 | socket.destroy();
244 | }
245 | });
246 |
247 | server.listen(8080);
248 | ```
249 |
250 | ### Server broadcast
251 |
252 | ```js
253 | const WebSocket = require('ws');
254 |
255 | const wss = new WebSocket.Server({ port: 8080 });
256 |
257 | // Broadcast to all.
258 | wss.broadcast = function broadcast(data) {
259 | wss.clients.forEach(function each(client) {
260 | if (client.readyState === WebSocket.OPEN) {
261 | client.send(data);
262 | }
263 | });
264 | };
265 |
266 | wss.on('connection', function connection(ws) {
267 | ws.on('message', function incoming(data) {
268 | // Broadcast to everyone else.
269 | wss.clients.forEach(function each(client) {
270 | if (client !== ws && client.readyState === WebSocket.OPEN) {
271 | client.send(data);
272 | }
273 | });
274 | });
275 | });
276 | ```
277 |
278 | ### echo.websocket.org demo
279 |
280 | ```js
281 | const WebSocket = require('ws');
282 |
283 | const ws = new WebSocket('wss://echo.websocket.org/', {
284 | origin: 'https://websocket.org'
285 | });
286 |
287 | ws.on('open', function open() {
288 | console.log('connected');
289 | ws.send(Date.now());
290 | });
291 |
292 | ws.on('close', function close() {
293 | console.log('disconnected');
294 | });
295 |
296 | ws.on('message', function incoming(data) {
297 | console.log(`Roundtrip time: ${Date.now() - data} ms`);
298 |
299 | setTimeout(function timeout() {
300 | ws.send(Date.now());
301 | }, 500);
302 | });
303 | ```
304 |
305 | ### Other examples
306 |
307 | For a full example with a browser client communicating with a ws server, see the
308 | examples folder.
309 |
310 | Otherwise, see the test cases.
311 |
312 | ## Error handling best practices
313 |
314 | ```js
315 | // If the WebSocket is closed before the following send is attempted
316 | ws.send('something');
317 |
318 | // Errors (both immediate and async write errors) can be detected in an optional
319 | // callback. The callback is also the only way of being notified that data has
320 | // actually been sent.
321 | ws.send('something', function ack(error) {
322 | // If error is not defined, the send has been completed, otherwise the error
323 | // object will indicate what failed.
324 | });
325 |
326 | // Immediate errors can also be handled with `try...catch`, but **note** that
327 | // since sends are inherently asynchronous, socket write failures will *not* be
328 | // captured when this technique is used.
329 | try { ws.send('something'); }
330 | catch (e) { /* handle error */ }
331 | ```
332 |
333 | ## FAQ
334 |
335 | ### How to get the IP address of the client?
336 |
337 | The remote IP address can be obtained from the raw socket.
338 |
339 | ```js
340 | const WebSocket = require('ws');
341 |
342 | const wss = new WebSocket.Server({ port: 8080 });
343 |
344 | wss.on('connection', function connection(ws, req) {
345 | const ip = req.connection.remoteAddress;
346 | });
347 | ```
348 |
349 | When the server runs behind a proxy like NGINX, the de-facto standard is to use
350 | the `X-Forwarded-For` header.
351 |
352 | ```js
353 | wss.on('connection', function connection(ws, req) {
354 | const ip = req.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
355 | });
356 | ```
357 |
358 | ### How to detect and close broken connections?
359 |
360 | Sometimes the link between the server and the client can be interrupted in a
361 | way that keeps both the server and the client unaware of the broken state of the
362 | connection (e.g. when pulling the cord).
363 |
364 | In these cases ping messages can be used as a means to verify that the remote
365 | endpoint is still responsive.
366 |
367 | ```js
368 | const WebSocket = require('ws');
369 |
370 | const wss = new WebSocket.Server({ port: 8080 });
371 |
372 | function noop() {}
373 |
374 | function heartbeat() {
375 | this.isAlive = true;
376 | }
377 |
378 | wss.on('connection', function connection(ws) {
379 | ws.isAlive = true;
380 | ws.on('pong', heartbeat);
381 | });
382 |
383 | const interval = setInterval(function ping() {
384 | wss.clients.forEach(function each(ws) {
385 | if (ws.isAlive === false) return ws.terminate();
386 |
387 | ws.isAlive = false;
388 | ws.ping(noop);
389 | });
390 | }, 30000);
391 | ```
392 |
393 | Pong messages are automatically sent in response to ping messages as required
394 | by the spec.
395 |
396 | ### How to connect via a proxy?
397 |
398 | Use a custom `http.Agent` implementation like [https-proxy-agent][] or
399 | [socks-proxy-agent][].
400 |
401 | ## Changelog
402 |
403 | We're using the GitHub [releases][changelog] for changelog entries.
404 |
405 | ## License
406 |
407 | [MIT](LICENSE)
408 |
409 | [https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
410 | [socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
411 | [client-report]: http://websockets.github.io/ws/autobahn/clients/
412 | [server-report]: http://websockets.github.io/ws/autobahn/servers/
413 | [permessage-deflate]: https://tools.ietf.org/html/rfc7692
414 | [changelog]: https://github.com/websockets/ws/releases
415 | [node-zlib-bug]: https://github.com/nodejs/node/issues/8871
416 | [node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
417 | [ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
418 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/receiver.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const stream = require('stream');
4 |
5 | const PerMessageDeflate = require('./permessage-deflate');
6 | const bufferUtil = require('./buffer-util');
7 | const validation = require('./validation');
8 | const constants = require('./constants');
9 |
10 | const GET_INFO = 0;
11 | const GET_PAYLOAD_LENGTH_16 = 1;
12 | const GET_PAYLOAD_LENGTH_64 = 2;
13 | const GET_MASK = 3;
14 | const GET_DATA = 4;
15 | const INFLATING = 5;
16 |
17 | /**
18 | * HyBi Receiver implementation.
19 | *
20 | * @extends stream.Writable
21 | */
22 | class Receiver extends stream.Writable {
23 | /**
24 | * Creates a Receiver instance.
25 | *
26 | * @param {String} binaryType The type for binary data
27 | * @param {Object} extensions An object containing the negotiated extensions
28 | * @param {Number} maxPayload The maximum allowed message length
29 | */
30 | constructor (binaryType, extensions, maxPayload) {
31 | super();
32 |
33 | this._binaryType = binaryType || constants.BINARY_TYPES[0];
34 | this[constants.kWebSocket] = undefined;
35 | this._extensions = extensions || {};
36 | this._maxPayload = maxPayload | 0;
37 |
38 | this._bufferedBytes = 0;
39 | this._buffers = [];
40 |
41 | this._compressed = false;
42 | this._payloadLength = 0;
43 | this._mask = undefined;
44 | this._fragmented = 0;
45 | this._masked = false;
46 | this._fin = false;
47 | this._opcode = 0;
48 |
49 | this._totalPayloadLength = 0;
50 | this._messageLength = 0;
51 | this._fragments = [];
52 |
53 | this._state = GET_INFO;
54 | this._loop = false;
55 | }
56 |
57 | /**
58 | * Implements `Writable.prototype._write()`.
59 | *
60 | * @param {Buffer} chunk The chunk of data to write
61 | * @param {String} encoding The character encoding of `chunk`
62 | * @param {Function} cb Callback
63 | */
64 | _write (chunk, encoding, cb) {
65 | if (this._opcode === 0x08) return cb();
66 |
67 | this._bufferedBytes += chunk.length;
68 | this._buffers.push(chunk);
69 | this.startLoop(cb);
70 | }
71 |
72 | /**
73 | * Consumes `n` bytes from the buffered data.
74 | *
75 | * @param {Number} n The number of bytes to consume
76 | * @return {Buffer} The consumed bytes
77 | * @private
78 | */
79 | consume (n) {
80 | this._bufferedBytes -= n;
81 |
82 | if (n === this._buffers[0].length) return this._buffers.shift();
83 |
84 | if (n < this._buffers[0].length) {
85 | const buf = this._buffers[0];
86 | this._buffers[0] = buf.slice(n);
87 | return buf.slice(0, n);
88 | }
89 |
90 | const dst = Buffer.allocUnsafe(n);
91 |
92 | do {
93 | const buf = this._buffers[0];
94 |
95 | if (n >= buf.length) {
96 | this._buffers.shift().copy(dst, dst.length - n);
97 | } else {
98 | buf.copy(dst, dst.length - n, 0, n);
99 | this._buffers[0] = buf.slice(n);
100 | }
101 |
102 | n -= buf.length;
103 | } while (n > 0);
104 |
105 | return dst;
106 | }
107 |
108 | /**
109 | * Starts the parsing loop.
110 | *
111 | * @param {Function} cb Callback
112 | * @private
113 | */
114 | startLoop (cb) {
115 | var err;
116 | this._loop = true;
117 |
118 | do {
119 | switch (this._state) {
120 | case GET_INFO:
121 | err = this.getInfo();
122 | break;
123 | case GET_PAYLOAD_LENGTH_16:
124 | err = this.getPayloadLength16();
125 | break;
126 | case GET_PAYLOAD_LENGTH_64:
127 | err = this.getPayloadLength64();
128 | break;
129 | case GET_MASK:
130 | this.getMask();
131 | break;
132 | case GET_DATA:
133 | err = this.getData(cb);
134 | break;
135 | default: // `INFLATING`
136 | this._loop = false;
137 | return;
138 | }
139 | } while (this._loop);
140 |
141 | cb(err);
142 | }
143 |
144 | /**
145 | * Reads the first two bytes of a frame.
146 | *
147 | * @return {(RangeError|undefined)} A possible error
148 | * @private
149 | */
150 | getInfo () {
151 | if (this._bufferedBytes < 2) {
152 | this._loop = false;
153 | return;
154 | }
155 |
156 | const buf = this.consume(2);
157 |
158 | if ((buf[0] & 0x30) !== 0x00) {
159 | this._loop = false;
160 | return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
161 | }
162 |
163 | const compressed = (buf[0] & 0x40) === 0x40;
164 |
165 | if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
166 | this._loop = false;
167 | return error(RangeError, 'RSV1 must be clear', true, 1002);
168 | }
169 |
170 | this._fin = (buf[0] & 0x80) === 0x80;
171 | this._opcode = buf[0] & 0x0f;
172 | this._payloadLength = buf[1] & 0x7f;
173 |
174 | if (this._opcode === 0x00) {
175 | if (compressed) {
176 | this._loop = false;
177 | return error(RangeError, 'RSV1 must be clear', true, 1002);
178 | }
179 |
180 | if (!this._fragmented) {
181 | this._loop = false;
182 | return error(RangeError, 'invalid opcode 0', true, 1002);
183 | }
184 |
185 | this._opcode = this._fragmented;
186 | } else if (this._opcode === 0x01 || this._opcode === 0x02) {
187 | if (this._fragmented) {
188 | this._loop = false;
189 | return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
190 | }
191 |
192 | this._compressed = compressed;
193 | } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
194 | if (!this._fin) {
195 | this._loop = false;
196 | return error(RangeError, 'FIN must be set', true, 1002);
197 | }
198 |
199 | if (compressed) {
200 | this._loop = false;
201 | return error(RangeError, 'RSV1 must be clear', true, 1002);
202 | }
203 |
204 | if (this._payloadLength > 0x7d) {
205 | this._loop = false;
206 | return error(
207 | RangeError,
208 | `invalid payload length ${this._payloadLength}`,
209 | true,
210 | 1002
211 | );
212 | }
213 | } else {
214 | this._loop = false;
215 | return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
216 | }
217 |
218 | if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
219 | this._masked = (buf[1] & 0x80) === 0x80;
220 |
221 | if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
222 | else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
223 | else return this.haveLength();
224 | }
225 |
226 | /**
227 | * Gets extended payload length (7+16).
228 | *
229 | * @return {(RangeError|undefined)} A possible error
230 | * @private
231 | */
232 | getPayloadLength16 () {
233 | if (this._bufferedBytes < 2) {
234 | this._loop = false;
235 | return;
236 | }
237 |
238 | this._payloadLength = this.consume(2).readUInt16BE(0);
239 | return this.haveLength();
240 | }
241 |
242 | /**
243 | * Gets extended payload length (7+64).
244 | *
245 | * @return {(RangeError|undefined)} A possible error
246 | * @private
247 | */
248 | getPayloadLength64 () {
249 | if (this._bufferedBytes < 8) {
250 | this._loop = false;
251 | return;
252 | }
253 |
254 | const buf = this.consume(8);
255 | const num = buf.readUInt32BE(0);
256 |
257 | //
258 | // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
259 | // if payload length is greater than this number.
260 | //
261 | if (num > Math.pow(2, 53 - 32) - 1) {
262 | this._loop = false;
263 | return error(
264 | RangeError,
265 | 'Unsupported WebSocket frame: payload length > 2^53 - 1',
266 | false,
267 | 1009
268 | );
269 | }
270 |
271 | this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
272 | return this.haveLength();
273 | }
274 |
275 | /**
276 | * Payload length has been read.
277 | *
278 | * @return {(RangeError|undefined)} A possible error
279 | * @private
280 | */
281 | haveLength () {
282 | if (this._payloadLength && this._opcode < 0x08) {
283 | this._totalPayloadLength += this._payloadLength;
284 | if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
285 | this._loop = false;
286 | return error(RangeError, 'Max payload size exceeded', false, 1009);
287 | }
288 | }
289 |
290 | if (this._masked) this._state = GET_MASK;
291 | else this._state = GET_DATA;
292 | }
293 |
294 | /**
295 | * Reads mask bytes.
296 | *
297 | * @private
298 | */
299 | getMask () {
300 | if (this._bufferedBytes < 4) {
301 | this._loop = false;
302 | return;
303 | }
304 |
305 | this._mask = this.consume(4);
306 | this._state = GET_DATA;
307 | }
308 |
309 | /**
310 | * Reads data bytes.
311 | *
312 | * @param {Function} cb Callback
313 | * @return {(Error|RangeError|undefined)} A possible error
314 | * @private
315 | */
316 | getData (cb) {
317 | var data = constants.EMPTY_BUFFER;
318 |
319 | if (this._payloadLength) {
320 | if (this._bufferedBytes < this._payloadLength) {
321 | this._loop = false;
322 | return;
323 | }
324 |
325 | data = this.consume(this._payloadLength);
326 | if (this._masked) bufferUtil.unmask(data, this._mask);
327 | }
328 |
329 | if (this._opcode > 0x07) return this.controlMessage(data);
330 |
331 | if (this._compressed) {
332 | this._state = INFLATING;
333 | this.decompress(data, cb);
334 | return;
335 | }
336 |
337 | if (data.length) {
338 | //
339 | // This message is not compressed so its lenght is the sum of the payload
340 | // length of all fragments.
341 | //
342 | this._messageLength = this._totalPayloadLength;
343 | this._fragments.push(data);
344 | }
345 |
346 | return this.dataMessage();
347 | }
348 |
349 | /**
350 | * Decompresses data.
351 | *
352 | * @param {Buffer} data Compressed data
353 | * @param {Function} cb Callback
354 | * @private
355 | */
356 | decompress (data, cb) {
357 | const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
358 |
359 | perMessageDeflate.decompress(data, this._fin, (err, buf) => {
360 | if (err) return cb(err);
361 |
362 | if (buf.length) {
363 | this._messageLength += buf.length;
364 | if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
365 | return cb(error(RangeError, 'Max payload size exceeded', false, 1009));
366 | }
367 |
368 | this._fragments.push(buf);
369 | }
370 |
371 | const er = this.dataMessage();
372 | if (er) return cb(er);
373 |
374 | this.startLoop(cb);
375 | });
376 | }
377 |
378 | /**
379 | * Handles a data message.
380 | *
381 | * @return {(Error|undefined)} A possible error
382 | * @private
383 | */
384 | dataMessage () {
385 | if (this._fin) {
386 | const messageLength = this._messageLength;
387 | const fragments = this._fragments;
388 |
389 | this._totalPayloadLength = 0;
390 | this._messageLength = 0;
391 | this._fragmented = 0;
392 | this._fragments = [];
393 |
394 | if (this._opcode === 2) {
395 | var data;
396 |
397 | if (this._binaryType === 'nodebuffer') {
398 | data = toBuffer(fragments, messageLength);
399 | } else if (this._binaryType === 'arraybuffer') {
400 | data = toArrayBuffer(toBuffer(fragments, messageLength));
401 | } else {
402 | data = fragments;
403 | }
404 |
405 | this.emit('message', data);
406 | } else {
407 | const buf = toBuffer(fragments, messageLength);
408 |
409 | if (!validation.isValidUTF8(buf)) {
410 | this._loop = false;
411 | return error(Error, 'invalid UTF-8 sequence', true, 1007);
412 | }
413 |
414 | this.emit('message', buf.toString());
415 | }
416 | }
417 |
418 | this._state = GET_INFO;
419 | }
420 |
421 | /**
422 | * Handles a control message.
423 | *
424 | * @param {Buffer} data Data to handle
425 | * @return {(Error|RangeError|undefined)} A possible error
426 | * @private
427 | */
428 | controlMessage (data) {
429 | if (this._opcode === 0x08) {
430 | this._loop = false;
431 |
432 | if (data.length === 0) {
433 | this.emit('conclude', 1005, '');
434 | this.end();
435 | } else if (data.length === 1) {
436 | return error(RangeError, 'invalid payload length 1', true, 1002);
437 | } else {
438 | const code = data.readUInt16BE(0);
439 |
440 | if (!validation.isValidStatusCode(code)) {
441 | return error(RangeError, `invalid status code ${code}`, true, 1002);
442 | }
443 |
444 | const buf = data.slice(2);
445 |
446 | if (!validation.isValidUTF8(buf)) {
447 | return error(Error, 'invalid UTF-8 sequence', true, 1007);
448 | }
449 |
450 | this.emit('conclude', code, buf.toString());
451 | this.end();
452 | }
453 |
454 | return;
455 | }
456 |
457 | if (this._opcode === 0x09) this.emit('ping', data);
458 | else this.emit('pong', data);
459 |
460 | this._state = GET_INFO;
461 | }
462 | }
463 |
464 | module.exports = Receiver;
465 |
466 | /**
467 | * Builds an error object.
468 | *
469 | * @param {(Error|RangeError)} ErrorCtor The error constructor
470 | * @param {String} message The error message
471 | * @param {Boolean} prefix Specifies whether or not to add a default prefix to
472 | * `message`
473 | * @param {Number} statusCode The status code
474 | * @return {(Error|RangeError)} The error
475 | * @private
476 | */
477 | function error (ErrorCtor, message, prefix, statusCode) {
478 | const err = new ErrorCtor(
479 | prefix ? `Invalid WebSocket frame: ${message}` : message
480 | );
481 |
482 | Error.captureStackTrace(err, error);
483 | err[constants.kStatusCode] = statusCode;
484 | return err;
485 | }
486 |
487 | /**
488 | * Makes a buffer from a list of fragments.
489 | *
490 | * @param {Buffer[]} fragments The list of fragments composing the message
491 | * @param {Number} messageLength The length of the message
492 | * @return {Buffer}
493 | * @private
494 | */
495 | function toBuffer (fragments, messageLength) {
496 | if (fragments.length === 1) return fragments[0];
497 | if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
498 | return constants.EMPTY_BUFFER;
499 | }
500 |
501 | /**
502 | * Converts a buffer to an `ArrayBuffer`.
503 | *
504 | * @param {Buffer} The buffer to convert
505 | * @return {ArrayBuffer} Converted buffer
506 | */
507 | function toArrayBuffer (buf) {
508 | if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
509 | return buf.buffer;
510 | }
511 |
512 | return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
513 | }
514 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/permessage-deflate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Limiter = require('async-limiter');
4 | const zlib = require('zlib');
5 |
6 | const bufferUtil = require('./buffer-util');
7 | const constants = require('./constants');
8 |
9 | const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
10 | const EMPTY_BLOCK = Buffer.from([0x00]);
11 |
12 | const kPerMessageDeflate = Symbol('permessage-deflate');
13 | const kWriteInProgress = Symbol('write-in-progress');
14 | const kPendingClose = Symbol('pending-close');
15 | const kTotalLength = Symbol('total-length');
16 | const kCallback = Symbol('callback');
17 | const kBuffers = Symbol('buffers');
18 | const kError = Symbol('error');
19 |
20 | //
21 | // We limit zlib concurrency, which prevents severe memory fragmentation
22 | // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
23 | // and https://github.com/websockets/ws/issues/1202
24 | //
25 | // Intentionally global; it's the global thread pool that's an issue.
26 | //
27 | let zlibLimiter;
28 |
29 | /**
30 | * permessage-deflate implementation.
31 | */
32 | class PerMessageDeflate {
33 | /**
34 | * Creates a PerMessageDeflate instance.
35 | *
36 | * @param {Object} options Configuration options
37 | * @param {Boolean} options.serverNoContextTakeover Request/accept disabling
38 | * of server context takeover
39 | * @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
40 | * disabling of client context takeover
41 | * @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
42 | * use of a custom server window size
43 | * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
44 | * for, or request, a custom client window size
45 | * @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate
46 | * @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate
47 | * @param {Number} options.threshold Size (in bytes) below which messages
48 | * should not be compressed
49 | * @param {Number} options.concurrencyLimit The number of concurrent calls to
50 | * zlib
51 | * @param {Boolean} isServer Create the instance in either server or client
52 | * mode
53 | * @param {Number} maxPayload The maximum allowed message length
54 | */
55 | constructor (options, isServer, maxPayload) {
56 | this._maxPayload = maxPayload | 0;
57 | this._options = options || {};
58 | this._threshold = this._options.threshold !== undefined
59 | ? this._options.threshold
60 | : 1024;
61 | this._isServer = !!isServer;
62 | this._deflate = null;
63 | this._inflate = null;
64 |
65 | this.params = null;
66 |
67 | if (!zlibLimiter) {
68 | const concurrency = this._options.concurrencyLimit !== undefined
69 | ? this._options.concurrencyLimit
70 | : 10;
71 | zlibLimiter = new Limiter({ concurrency });
72 | }
73 | }
74 |
75 | /**
76 | * @type {String}
77 | */
78 | static get extensionName () {
79 | return 'permessage-deflate';
80 | }
81 |
82 | /**
83 | * Create an extension negotiation offer.
84 | *
85 | * @return {Object} Extension parameters
86 | * @public
87 | */
88 | offer () {
89 | const params = {};
90 |
91 | if (this._options.serverNoContextTakeover) {
92 | params.server_no_context_takeover = true;
93 | }
94 | if (this._options.clientNoContextTakeover) {
95 | params.client_no_context_takeover = true;
96 | }
97 | if (this._options.serverMaxWindowBits) {
98 | params.server_max_window_bits = this._options.serverMaxWindowBits;
99 | }
100 | if (this._options.clientMaxWindowBits) {
101 | params.client_max_window_bits = this._options.clientMaxWindowBits;
102 | } else if (this._options.clientMaxWindowBits == null) {
103 | params.client_max_window_bits = true;
104 | }
105 |
106 | return params;
107 | }
108 |
109 | /**
110 | * Accept an extension negotiation offer/response.
111 | *
112 | * @param {Array} configurations The extension negotiation offers/reponse
113 | * @return {Object} Accepted configuration
114 | * @public
115 | */
116 | accept (configurations) {
117 | configurations = this.normalizeParams(configurations);
118 |
119 | this.params = this._isServer
120 | ? this.acceptAsServer(configurations)
121 | : this.acceptAsClient(configurations);
122 |
123 | return this.params;
124 | }
125 |
126 | /**
127 | * Releases all resources used by the extension.
128 | *
129 | * @public
130 | */
131 | cleanup () {
132 | if (this._inflate) {
133 | if (this._inflate[kWriteInProgress]) {
134 | this._inflate[kPendingClose] = true;
135 | } else {
136 | this._inflate.close();
137 | this._inflate = null;
138 | }
139 | }
140 | if (this._deflate) {
141 | if (this._deflate[kWriteInProgress]) {
142 | this._deflate[kPendingClose] = true;
143 | } else {
144 | this._deflate.close();
145 | this._deflate = null;
146 | }
147 | }
148 | }
149 |
150 | /**
151 | * Accept an extension negotiation offer.
152 | *
153 | * @param {Array} offers The extension negotiation offers
154 | * @return {Object} Accepted configuration
155 | * @private
156 | */
157 | acceptAsServer (offers) {
158 | const opts = this._options;
159 | const accepted = offers.find((params) => {
160 | if (
161 | (opts.serverNoContextTakeover === false &&
162 | params.server_no_context_takeover) ||
163 | (params.server_max_window_bits &&
164 | (opts.serverMaxWindowBits === false ||
165 | (typeof opts.serverMaxWindowBits === 'number' &&
166 | opts.serverMaxWindowBits > params.server_max_window_bits))) ||
167 | (typeof opts.clientMaxWindowBits === 'number' &&
168 | !params.client_max_window_bits)
169 | ) {
170 | return false;
171 | }
172 |
173 | return true;
174 | });
175 |
176 | if (!accepted) {
177 | throw new Error('None of the extension offers can be accepted');
178 | }
179 |
180 | if (opts.serverNoContextTakeover) {
181 | accepted.server_no_context_takeover = true;
182 | }
183 | if (opts.clientNoContextTakeover) {
184 | accepted.client_no_context_takeover = true;
185 | }
186 | if (typeof opts.serverMaxWindowBits === 'number') {
187 | accepted.server_max_window_bits = opts.serverMaxWindowBits;
188 | }
189 | if (typeof opts.clientMaxWindowBits === 'number') {
190 | accepted.client_max_window_bits = opts.clientMaxWindowBits;
191 | } else if (
192 | accepted.client_max_window_bits === true ||
193 | opts.clientMaxWindowBits === false
194 | ) {
195 | delete accepted.client_max_window_bits;
196 | }
197 |
198 | return accepted;
199 | }
200 |
201 | /**
202 | * Accept the extension negotiation response.
203 | *
204 | * @param {Array} response The extension negotiation response
205 | * @return {Object} Accepted configuration
206 | * @private
207 | */
208 | acceptAsClient (response) {
209 | const params = response[0];
210 |
211 | if (
212 | this._options.clientNoContextTakeover === false &&
213 | params.client_no_context_takeover
214 | ) {
215 | throw new Error('Unexpected parameter "client_no_context_takeover"');
216 | }
217 |
218 | if (!params.client_max_window_bits) {
219 | if (typeof this._options.clientMaxWindowBits === 'number') {
220 | params.client_max_window_bits = this._options.clientMaxWindowBits;
221 | }
222 | } else if (
223 | this._options.clientMaxWindowBits === false ||
224 | (typeof this._options.clientMaxWindowBits === 'number' &&
225 | params.client_max_window_bits > this._options.clientMaxWindowBits)
226 | ) {
227 | throw new Error(
228 | 'Unexpected or invalid parameter "client_max_window_bits"'
229 | );
230 | }
231 |
232 | return params;
233 | }
234 |
235 | /**
236 | * Normalize parameters.
237 | *
238 | * @param {Array} configurations The extension negotiation offers/reponse
239 | * @return {Array} The offers/response with normalized parameters
240 | * @private
241 | */
242 | normalizeParams (configurations) {
243 | configurations.forEach((params) => {
244 | Object.keys(params).forEach((key) => {
245 | var value = params[key];
246 |
247 | if (value.length > 1) {
248 | throw new Error(`Parameter "${key}" must have only a single value`);
249 | }
250 |
251 | value = value[0];
252 |
253 | if (key === 'client_max_window_bits') {
254 | if (value !== true) {
255 | const num = +value;
256 | if (!Number.isInteger(num) || num < 8 || num > 15) {
257 | throw new TypeError(
258 | `Invalid value for parameter "${key}": ${value}`
259 | );
260 | }
261 | value = num;
262 | } else if (!this._isServer) {
263 | throw new TypeError(
264 | `Invalid value for parameter "${key}": ${value}`
265 | );
266 | }
267 | } else if (key === 'server_max_window_bits') {
268 | const num = +value;
269 | if (!Number.isInteger(num) || num < 8 || num > 15) {
270 | throw new TypeError(
271 | `Invalid value for parameter "${key}": ${value}`
272 | );
273 | }
274 | value = num;
275 | } else if (
276 | key === 'client_no_context_takeover' ||
277 | key === 'server_no_context_takeover'
278 | ) {
279 | if (value !== true) {
280 | throw new TypeError(
281 | `Invalid value for parameter "${key}": ${value}`
282 | );
283 | }
284 | } else {
285 | throw new Error(`Unknown parameter "${key}"`);
286 | }
287 |
288 | params[key] = value;
289 | });
290 | });
291 |
292 | return configurations;
293 | }
294 |
295 | /**
296 | * Decompress data. Concurrency limited by async-limiter.
297 | *
298 | * @param {Buffer} data Compressed data
299 | * @param {Boolean} fin Specifies whether or not this is the last fragment
300 | * @param {Function} callback Callback
301 | * @public
302 | */
303 | decompress (data, fin, callback) {
304 | zlibLimiter.push((done) => {
305 | this._decompress(data, fin, (err, result) => {
306 | done();
307 | callback(err, result);
308 | });
309 | });
310 | }
311 |
312 | /**
313 | * Compress data. Concurrency limited by async-limiter.
314 | *
315 | * @param {Buffer} data Data to compress
316 | * @param {Boolean} fin Specifies whether or not this is the last fragment
317 | * @param {Function} callback Callback
318 | * @public
319 | */
320 | compress (data, fin, callback) {
321 | zlibLimiter.push((done) => {
322 | this._compress(data, fin, (err, result) => {
323 | done();
324 | callback(err, result);
325 | });
326 | });
327 | }
328 |
329 | /**
330 | * Decompress data.
331 | *
332 | * @param {Buffer} data Compressed data
333 | * @param {Boolean} fin Specifies whether or not this is the last fragment
334 | * @param {Function} callback Callback
335 | * @private
336 | */
337 | _decompress (data, fin, callback) {
338 | const endpoint = this._isServer ? 'client' : 'server';
339 |
340 | if (!this._inflate) {
341 | const key = `${endpoint}_max_window_bits`;
342 | const windowBits = typeof this.params[key] !== 'number'
343 | ? zlib.Z_DEFAULT_WINDOWBITS
344 | : this.params[key];
345 |
346 | this._inflate = zlib.createInflateRaw(
347 | Object.assign({}, this._options.zlibInflateOptions, { windowBits })
348 | );
349 | this._inflate[kPerMessageDeflate] = this;
350 | this._inflate[kTotalLength] = 0;
351 | this._inflate[kBuffers] = [];
352 | this._inflate.on('error', inflateOnError);
353 | this._inflate.on('data', inflateOnData);
354 | }
355 |
356 | this._inflate[kCallback] = callback;
357 | this._inflate[kWriteInProgress] = true;
358 |
359 | this._inflate.write(data);
360 | if (fin) this._inflate.write(TRAILER);
361 |
362 | this._inflate.flush(() => {
363 | const err = this._inflate[kError];
364 |
365 | if (err) {
366 | this._inflate.close();
367 | this._inflate = null;
368 | callback(err);
369 | return;
370 | }
371 |
372 | const data = bufferUtil.concat(
373 | this._inflate[kBuffers],
374 | this._inflate[kTotalLength]
375 | );
376 |
377 | if (
378 | (fin && this.params[`${endpoint}_no_context_takeover`]) ||
379 | this._inflate[kPendingClose]
380 | ) {
381 | this._inflate.close();
382 | this._inflate = null;
383 | } else {
384 | this._inflate[kWriteInProgress] = false;
385 | this._inflate[kTotalLength] = 0;
386 | this._inflate[kBuffers] = [];
387 | }
388 |
389 | callback(null, data);
390 | });
391 | }
392 |
393 | /**
394 | * Compress data.
395 | *
396 | * @param {Buffer} data Data to compress
397 | * @param {Boolean} fin Specifies whether or not this is the last fragment
398 | * @param {Function} callback Callback
399 | * @private
400 | */
401 | _compress (data, fin, callback) {
402 | if (!data || data.length === 0) {
403 | process.nextTick(callback, null, EMPTY_BLOCK);
404 | return;
405 | }
406 |
407 | const endpoint = this._isServer ? 'server' : 'client';
408 |
409 | if (!this._deflate) {
410 | const key = `${endpoint}_max_window_bits`;
411 | const windowBits = typeof this.params[key] !== 'number'
412 | ? zlib.Z_DEFAULT_WINDOWBITS
413 | : this.params[key];
414 |
415 | this._deflate = zlib.createDeflateRaw(
416 | Object.assign(
417 | // TODO deprecate memLevel/level and recommend zlibDeflateOptions instead
418 | {
419 | memLevel: this._options.memLevel,
420 | level: this._options.level
421 | },
422 | this._options.zlibDeflateOptions,
423 | { windowBits }
424 | )
425 | );
426 |
427 | this._deflate[kTotalLength] = 0;
428 | this._deflate[kBuffers] = [];
429 |
430 | //
431 | // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use
432 | // it is made after it has already been closed. This cannot happen here,
433 | // so we only add a listener for the `'data'` event.
434 | //
435 | this._deflate.on('data', deflateOnData);
436 | }
437 |
438 | this._deflate[kWriteInProgress] = true;
439 |
440 | this._deflate.write(data);
441 | this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
442 | var data = bufferUtil.concat(
443 | this._deflate[kBuffers],
444 | this._deflate[kTotalLength]
445 | );
446 |
447 | if (fin) data = data.slice(0, data.length - 4);
448 |
449 | if (
450 | (fin && this.params[`${endpoint}_no_context_takeover`]) ||
451 | this._deflate[kPendingClose]
452 | ) {
453 | this._deflate.close();
454 | this._deflate = null;
455 | } else {
456 | this._deflate[kWriteInProgress] = false;
457 | this._deflate[kTotalLength] = 0;
458 | this._deflate[kBuffers] = [];
459 | }
460 |
461 | callback(null, data);
462 | });
463 | }
464 | }
465 |
466 | module.exports = PerMessageDeflate;
467 |
468 | /**
469 | * The listener of the `zlib.DeflateRaw` stream `'data'` event.
470 | *
471 | * @param {Buffer} chunk A chunk of data
472 | * @private
473 | */
474 | function deflateOnData (chunk) {
475 | this[kBuffers].push(chunk);
476 | this[kTotalLength] += chunk.length;
477 | }
478 |
479 | /**
480 | * The listener of the `zlib.InflateRaw` stream `'data'` event.
481 | *
482 | * @param {Buffer} chunk A chunk of data
483 | * @private
484 | */
485 | function inflateOnData (chunk) {
486 | this[kTotalLength] += chunk.length;
487 |
488 | if (
489 | this[kPerMessageDeflate]._maxPayload < 1 ||
490 | this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
491 | ) {
492 | this[kBuffers].push(chunk);
493 | return;
494 | }
495 |
496 | this[kError] = new RangeError('Max payload size exceeded');
497 | this[kError][constants.kStatusCode] = 1009;
498 | this.removeListener('data', inflateOnData);
499 | this.reset();
500 | }
501 |
502 | /**
503 | * The listener of the `zlib.InflateRaw` stream `'error'` event.
504 | *
505 | * @param {Error} err The emitted error
506 | * @private
507 | */
508 | function inflateOnError (err) {
509 | //
510 | // There is no need to call `Zlib#close()` as the handle is automatically
511 | // closed when an error is emitted.
512 | //
513 | this[kPerMessageDeflate]._inflate = null;
514 | err[constants.kStatusCode] = 1007;
515 | this[kCallback](err);
516 | }
517 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/event-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * EventService module
3 | * @module event-service
4 | */
5 |
6 | const kRequest = "request";
7 | const kRequestAcknowledge = "requestAck";
8 | const kRequestExecute = "requestExecute";
9 | const kRequestResult = "requestResult";
10 |
11 | const kEvent = "event";
12 | const kLog = "log";
13 | const kRequestDefaultTimeout = 700;
14 |
15 | const kAddress = "127.0.0.1";
16 |
17 | /**
18 | * Callback used to handle an Emit or Request.
19 | *
20 | * @callback onHandler
21 | * @param {string} eventType The type of the event. Corresponds to the eventType used in Emit and Request.
22 | * @param {object[]} data The data sent by Emit or Request.
23 | * @returns {*} Nothing in the case of a Emit handler, otherwise anything.
24 | */
25 |
26 | /**
27 | * Callback used to handle the result of a Request.
28 | *
29 | * @callback promiseHandler
30 | * @param {Error} err The error, if there was an error or the request has been cancelled. Null otherwise.
31 | * @param {object[]} data The result of all request handlers.
32 | */
33 |
34 | /**
35 | * Callback used to unregister an event handler registered with the function On.
36 | *
37 | * @callback offHandler
38 | */
39 |
40 | /**
41 | * Map of event handlers.
42 | * @type {Map}
43 | */
44 | let s_Events = new Map();
45 |
46 | /**
47 | * Map of requests.
48 | * @type {Map}
49 | */
50 | let s_Requests = new Map();
51 |
52 | let s_Ws = null;
53 | let s_Connected = false;
54 | let s_ConnectionId = -1;
55 |
56 | let s_RequestId = 0;
57 |
58 | let s_TickTimer = null;
59 |
60 | /**
61 | * Enum for event data serialization.
62 | * @readonly
63 | * @enum {number}
64 | */
65 | const EventDataSerialization = {
66 | /** @type {number} */
67 | StandardJson: 0,
68 | /** @type {number} */
69 | JsonUtility: 1
70 | }
71 |
72 | class RequestData {
73 | /** @type {string} */
74 | eventType;
75 | /** @type {number} */
76 | id;
77 | /** @type {promiseHandler[]} */
78 | promises;
79 | /** @type {number} */
80 | offerStartTime;
81 | /** @type {boolean} */
82 | isAcknowledged;
83 | /** @type {object} */
84 | data;
85 | /** @type {number} */
86 | timeoutInMs;
87 | /** @type {object} */
88 | dataInfos;
89 | }
90 |
91 | class RequestMessage {
92 | /** @type {string} */
93 | reqType;
94 | /** @type {string} */
95 | eventType;
96 | /** @type {number} */
97 | senderId;
98 | /** @type {number} */
99 | requestId;
100 | /** @type {object} */
101 | data;
102 | /** @type {object} */
103 | dataInfos;
104 | /** @type {EventDataSerialization} */
105 | eventDataSerialization;
106 | }
107 |
108 | // API functions
109 |
110 | export class OperationCanceledException extends Error {
111 | /**
112 | * Construct a new OperationCanceledException.
113 | * @param {string} message The error message.
114 | */
115 | constructor(message) {
116 | super(message);
117 | this.name = 'OperationCanceledException';
118 | }
119 | }
120 |
121 | export class TimeoutException extends Error {
122 | /**
123 | * Construct a new TimeoutException.
124 | * @param {string} message The error message.
125 | */
126 | constructor(message) {
127 | super(message);
128 | this.name = 'TimeoutException';
129 | }
130 | }
131 |
132 | /**
133 | * Start the EventService.
134 | * @param {number} port Port on which to connect to.
135 | */
136 | export function Start(port) {
137 | const connectTo = `ws://${kAddress}:${port}/event`;
138 | s_Ws = new WebSocket(connectTo);
139 | s_Ws.onopen = OnOpen;
140 | s_Ws.onclose = OnClose;
141 | s_Ws.onerror = OnError;
142 | s_Ws.onmessage = OnMessage;
143 |
144 | s_TickTimer = setInterval(Tick, 0);
145 | }
146 |
147 | /**
148 | * Close the EventService. This function is called automatically when the websocket is closed or there is an error.
149 | */
150 | export function Close() {
151 | s_Connected = false;
152 | s_ConnectionId = -1;
153 | Clear();
154 |
155 | clearInterval(s_TickTimer);
156 | }
157 |
158 | /**
159 | * Clears all pending requests.
160 | */
161 | export function Clear() {
162 | s_Requests.clear();
163 | }
164 |
165 | /**
166 | * Register an event handler for a specific event raised by Emit or Request.
167 | * @param {string} eventType The type of event to handle.
168 | * @param {onHandler} onHandler Callback that handles the event.
169 | * @returns {offHandler} Callback that you can call to unregister the handler.
170 | */
171 | export function On(eventType, onHandler) {
172 | let handlers = null;
173 | if (!s_Events.has(eventType)) {
174 | handlers = [];
175 | s_Events.set(eventType, handlers);
176 | }
177 | handlers = s_Events.get(eventType);
178 | if (handlers.includes(onHandler)) {
179 | throw "Cannot add existing event handler: " + eventType;
180 | }
181 | handlers.push(onHandler);
182 |
183 | return () => {
184 | Off(eventType, onHandler)
185 | }
186 | }
187 |
188 | /**
189 | * Unregister an event handler.
190 | * @param {string} eventType The type of event to handle.
191 | * @param {onHandler} onHandler The handler to unregister.
192 | */
193 | export function Off(eventType, onHandler) {
194 | if (s_Events.has(eventType)) {
195 | let handlers = s_Events.get(eventType);
196 | let index = handlers.indexOf(onHandler)
197 | if (index > -1)
198 | handlers.splice(index, 1);
199 | if (handlers.length === 0) {
200 | s_Events.delete(eventType);
201 | }
202 | }
203 | }
204 |
205 | /**
206 | * Check if the EventService is connected to the server.
207 | * @return {boolean} True if the EventService is connected.
208 | */
209 | export function IsConnected() {
210 | return s_Connected;
211 | }
212 |
213 | /**
214 | * Emit an event with some values.
215 | * @param {string} eventType The type of event to emit.
216 | * @param {object|object[]} args Values to send. Can be null.
217 | * @param {number} [targetId=-1] Target client Id. -1 for any client.
218 | */
219 | export function Emit(eventType, args, targetId = -1) {
220 | if (!Array.isArray(args) && !!args) {
221 | args = [args];
222 | }
223 |
224 | let notifyWildcard = true;
225 | let req = CreateRequest(kEvent, eventType, targetId, -1, args);
226 |
227 | NotifyLocalListeners(eventType, args, notifyWildcard);
228 |
229 | SendRequest(req);
230 | }
231 |
232 | /**
233 | * Check if there is any request pending for a specific event.
234 | * @param {string} eventType Type of event.
235 | * @returns {boolean} True if there is any request pending.
236 | */
237 | export function IsRequestPending(eventType) {
238 | return s_Requests.has(eventType);
239 | }
240 |
241 | /**
242 | * Cancel any pending requests for a specific event.
243 | * @param {string} eventType Type of event.
244 | * @param {string} [message=null] Message to send to the request handler.
245 | * @returns {boolean} True if the request was canceled.
246 | */
247 | export function CancelRequest(eventType, message = null) {
248 | if (!s_Requests.has(eventType))
249 | return false;
250 |
251 | let request = s_Requests.get(eventType);
252 | CleanRequest(eventType);
253 | Reject(request, new OperationCanceledException(!!message ? message : `Request ${eventType} canceled`));
254 | return true;
255 | }
256 |
257 | /**
258 | * Send a request to anyone who is connected to the EventService.
259 | * @param {string} eventType Type of event to request.
260 | * @param {promiseHandler} promiseHandler Callback that handles the result of the request.
261 | * @param {object|object[]} args Values to send with the request. Can be null.
262 | * @param {number} [timeoutInMs=] Number of milliseconds to wait before cancelling the request automatically.
263 | */
264 | export function Request(eventType, promiseHandler, args, timeoutInMs = kRequestDefaultTimeout) {
265 | if (!Array.isArray(args) && !!args) {
266 | args = [args];
267 | }
268 |
269 | if (s_Requests.has(eventType)) {
270 | let request = s_Requests.get(eventType);
271 | request.promises.push(promiseHandler);
272 | return;
273 | }
274 |
275 | let request = new RequestData();
276 | request.eventType = eventType;
277 | request.promises = [promiseHandler];
278 | request.timeoutInMs = timeoutInMs;
279 |
280 | if (HasHandlers(eventType)) {
281 | let results = NotifyLocalListeners(eventType, args, false);
282 |
283 | let exception = results.find(r => r instanceof Error);
284 | if (exception) {
285 | Reject(request, exception);
286 | }
287 | else {
288 | Resolve(request, results);
289 | }
290 | }
291 | else {
292 | request.offerStartTime = Date.now();
293 | let requestId = GetNewRequestId();
294 | request.id = requestId;
295 |
296 | let msg = CreateRequest(kRequest, eventType, -1, requestId, args);
297 | SendRequest(msg);
298 |
299 | s_Requests.set(eventType, request);
300 | request.data = msg.data;
301 | request.dataInfos = msg.dataInfos;
302 | }
303 | }
304 |
305 | /**
306 | * Send a message to the server to be printed in the console.
307 | * @param {string} msg Message to log.
308 | */
309 | export function Log(msg) {
310 | let req = CreateRequest(kLog, null, -1, -1, msg, null);
311 | SendRequest(req);
312 | }
313 |
314 | // Private functions
315 | function OnOpen(ev) {
316 | }
317 |
318 | function OnClose(ev) {
319 | Close();
320 | }
321 |
322 | function OnError(ev) {
323 | Close();
324 | console.error(ev);
325 | }
326 |
327 | function OnMessage(ev) {
328 | if (IsConnected()) {
329 | HandleIncomingEvent(ev)
330 | } else {
331 | // The server sends us our connectionId in plain text
332 | let data = ev["data"];
333 | s_ConnectionId = parseInt(data);
334 | s_Connected = true;
335 | }
336 | }
337 |
338 | function CreateRequest(msgType, eventType, targetId, requestId, args, dataInfos) {
339 | let req = {};
340 | req["req"] = msgType;
341 | if (targetId != -1) {
342 | req["targetId"] = targetId;
343 | }
344 | if (!!eventType)
345 | req["type"] = eventType;
346 | req["senderId"] = s_ConnectionId;
347 | if (requestId)
348 | req["requestId"] = requestId;
349 | req["data"] = args;
350 | if (dataInfos)
351 | req["dataInfos"] = dataInfos;
352 |
353 | return req;
354 | }
355 |
356 | function SendRequest(request) {
357 | s_Ws.send(JSON.stringify(request));
358 | }
359 |
360 | function NotifyLocalListeners(eventType, data, notifyWildcard) {
361 | let result = [];
362 | if (s_Events.has(eventType)) {
363 | let handlers = s_Events.get(eventType);
364 | try {
365 | for (const handler of handlers) {
366 | result.push(handler(eventType, data));
367 | }
368 | }
369 | catch (ex) {
370 | result = [ex];
371 | console.error(ex);
372 | }
373 | }
374 |
375 | if (notifyWildcard && s_Events.has("*")) {
376 | let handlers = s_Events.get(eventType);
377 | try {
378 | for (const handler of handlers) {
379 | handler(eventType, data);
380 | }
381 | }
382 | catch (ex) {
383 | console.error(ex);
384 | }
385 | }
386 |
387 | return result;
388 | }
389 |
390 | function HandleIncomingEvent(event) {
391 | let msg = DeserializeEvent(event);
392 | if (!msg)
393 | return;
394 |
395 | switch (msg.reqType) {
396 | case kRequest: // Receiver
397 | // We are able to answer this request. Acknowledge it to the sender:
398 | if (HasHandlers(msg.eventType)) {
399 | let response = CreateRequest(kRequestAcknowledge, msg.eventType, msg.senderId, msg.requestId, null, null);
400 | SendRequest(response);
401 | }
402 | break;
403 | case kRequestAcknowledge: // Request emitter
404 | let pendingRequest = GetPendingRequest(msg.eventType, msg.requestId);
405 | if (pendingRequest != null) {
406 | // A client is able to fulfill the request: proceed with request execution:
407 | pendingRequest.isAcknowledged = true;
408 | pendingRequest.offerStartTime = Date.now();
409 |
410 | let message = CreateRequest(kRequestExecute, msg.eventType, msg.senderId, msg.requestId, pendingRequest.data, pendingRequest.dataInfos);
411 | SendRequest(message);
412 | }
413 | // else Request might potentially have timed out.
414 | break;
415 | case kRequestExecute: // Request receiver
416 | {
417 | // We are fulfilling the request: send the execution results
418 | let results = NotifyLocalListeners(msg.eventType, msg.data, false);
419 | let response = CreateRequest(kRequestResult, msg.eventType, msg.senderId, msg.requestId, results, msg.eventDataSerialization);
420 | SendRequest(response);
421 | break;
422 | }
423 | case kRequestResult: // Request emitter
424 | let pendingRequestAwaitingResult = GetPendingRequest(msg.eventType, msg.requestId);
425 | if (pendingRequestAwaitingResult != null) {
426 | let timeForSuccess = Date.now() - pendingRequestAwaitingResult.offerStartTime;
427 | console.log(`[UMPE] Request ${msg.eventType} successful in ${timeForSuccess} ms`);
428 | Resolve(pendingRequestAwaitingResult, msg.data);
429 | CleanRequest(msg.eventType);
430 | }
431 | break;
432 | case kEvent:
433 | {
434 | NotifyLocalListeners(msg.eventType, msg.data, true);
435 | break;
436 | }
437 | }
438 | }
439 |
440 | function DeserializeEvent(event) {
441 | let msg = JSON.parse(event.data);
442 |
443 | if (!msg) {
444 | console.error("Invalid message: " + event);
445 | return null;
446 | }
447 |
448 | if (!msg.hasOwnProperty("type")) {
449 | console.error("Message doesn't contain type: " + event);
450 | return null;
451 | }
452 |
453 | if (!msg.hasOwnProperty("req")) {
454 | console.error("Message doesn't contain req: " + event);
455 | return null;
456 | }
457 |
458 | if (!msg.hasOwnProperty("senderId")) {
459 | console.error("Message doesn't contain senderId: " + event);
460 | return null;
461 | }
462 |
463 | // If we are receiving our own messages, bail out.
464 | if (msg.senderId === s_ConnectionId) {
465 | return null;
466 | }
467 |
468 | let deserializedMsg = new RequestMessage();
469 |
470 | deserializedMsg.reqType = msg.req;
471 | deserializedMsg.eventType = msg.type;
472 | deserializedMsg.senderId = msg.senderId;
473 | deserializedMsg.requestId = -1;
474 | if (msg.hasOwnProperty("requestId"))
475 | deserializedMsg.requestId = msg.requestId;
476 |
477 | if (msg.hasOwnProperty("data"))
478 | deserializedMsg.data = msg.data;
479 | if (msg.hasOwnProperty("dataInfos")) {
480 | deserializedMsg.dataInfos = msg.dataInfos;
481 | deserializedMsg.eventDataSerialization = EventDataSerialization.JsonUtility;
482 | }
483 |
484 | return deserializedMsg;
485 | }
486 |
487 | function CleanRequest(eventType) {
488 | s_Requests.delete(eventType);
489 | }
490 |
491 | function Resolve(offer, results) {
492 | for (let i = 0, end = offer.promises.length; i != end; ++i) {
493 | try {
494 | offer.promises[i](null, results);
495 | }
496 | catch (e) {
497 | console.error(e);
498 | }
499 | }
500 | }
501 |
502 | function Reject(offer, err) {
503 | for (let i = 0, end = offer.promises.length; i != end; ++i) {
504 | try {
505 | offer.promises[i](err, null);
506 | }
507 | catch (e) {
508 | console.error(e);
509 | }
510 | }
511 | }
512 |
513 | function HasHandlers(eventType) {
514 | return s_Events.has(eventType) && s_Events.get(eventType).length > 0;
515 | }
516 |
517 | function GetNewRequestId() {
518 | return ++s_RequestId;
519 | }
520 |
521 | function GetPendingRequest(eventType, requestId) {
522 | if (!s_Requests.has(eventType)) {
523 | return null;
524 | }
525 | let pendingRequest = s_Requests.get(eventType);
526 | if (pendingRequest != null && pendingRequest.id != requestId) {
527 | console.log(`Request Id mismatch: (Pending)${pendingRequest.id} vs (request)${requestId}`);
528 | // Mismatch request: clean it.
529 | CleanRequest(eventType);
530 | pendingRequest = null;
531 | }
532 | return pendingRequest != null && pendingRequest.id == requestId ? pendingRequest : null;
533 | }
534 |
535 | function Tick() {
536 | if (!IsConnected())
537 | return;
538 |
539 | if (s_Requests.size > 0) {
540 | let now = Date.now();
541 | for (let request of s_Requests.values()) {
542 | let elapsedTime = now - request.offerStartTime;
543 | if (request.isAcknowledged)
544 | continue;
545 | if (elapsedTime > request.timeoutInMs) {
546 | CleanRequest(request.eventType);
547 | Reject(request, new TimeoutException(`Request timeout for ${request.eventType} (${elapsedTime} > ${request.timeoutInMs})`));
548 | }
549 | }
550 | }
551 | }
552 |
--------------------------------------------------------------------------------
/ChannelClientNodeApp~/node_modules/ws/lib/websocket.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EventEmitter = require('events');
4 | const crypto = require('crypto');
5 | const https = require('https');
6 | const http = require('http');
7 | const net = require('net');
8 | const tls = require('tls');
9 | const url = require('url');
10 |
11 | const PerMessageDeflate = require('./permessage-deflate');
12 | const EventTarget = require('./event-target');
13 | const extension = require('./extension');
14 | const constants = require('./constants');
15 | const Receiver = require('./receiver');
16 | const Sender = require('./sender');
17 |
18 | const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
19 | const kWebSocket = constants.kWebSocket;
20 | const protocolVersions = [8, 13];
21 | const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
22 |
23 | /**
24 | * Class representing a WebSocket.
25 | *
26 | * @extends EventEmitter
27 | */
28 | class WebSocket extends EventEmitter {
29 | /**
30 | * Create a new `WebSocket`.
31 | *
32 | * @param {(String|url.Url|url.URL)} address The URL to which to connect
33 | * @param {(String|String[])} protocols The subprotocols
34 | * @param {Object} options Connection options
35 | */
36 | constructor (address, protocols, options) {
37 | super();
38 |
39 | this.readyState = WebSocket.CONNECTING;
40 | this.protocol = '';
41 |
42 | this._binaryType = constants.BINARY_TYPES[0];
43 | this._closeFrameReceived = false;
44 | this._closeFrameSent = false;
45 | this._closeMessage = '';
46 | this._closeTimer = null;
47 | this._closeCode = 1006;
48 | this._extensions = {};
49 | this._isServer = true;
50 | this._receiver = null;
51 | this._sender = null;
52 | this._socket = null;
53 |
54 | if (address !== null) {
55 | if (Array.isArray(protocols)) {
56 | protocols = protocols.join(', ');
57 | } else if (typeof protocols === 'object' && protocols !== null) {
58 | options = protocols;
59 | protocols = undefined;
60 | }
61 |
62 | initAsClient.call(this, address, protocols, options);
63 | }
64 | }
65 |
66 | get CONNECTING () { return WebSocket.CONNECTING; }
67 | get CLOSING () { return WebSocket.CLOSING; }
68 | get CLOSED () { return WebSocket.CLOSED; }
69 | get OPEN () { return WebSocket.OPEN; }
70 |
71 | /**
72 | * This deviates from the WHATWG interface since ws doesn't support the required
73 | * default "blob" type (instead we define a custom "nodebuffer" type).
74 | *
75 | * @type {String}
76 | */
77 | get binaryType () {
78 | return this._binaryType;
79 | }
80 |
81 | set binaryType (type) {
82 | if (constants.BINARY_TYPES.indexOf(type) < 0) return;
83 |
84 | this._binaryType = type;
85 |
86 | //
87 | // Allow to change `binaryType` on the fly.
88 | //
89 | if (this._receiver) this._receiver._binaryType = type;
90 | }
91 |
92 | /**
93 | * @type {Number}
94 | */
95 | get bufferedAmount () {
96 | if (!this._socket) return 0;
97 |
98 | //
99 | // `socket.bufferSize` is `undefined` if the socket is closed.
100 | //
101 | return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
102 | }
103 |
104 | /**
105 | * @type {String}
106 | */
107 | get extensions () {
108 | return Object.keys(this._extensions).join();
109 | }
110 |
111 | /**
112 | * Set up the socket and the internal resources.
113 | *
114 | * @param {net.Socket} socket The network socket between the server and client
115 | * @param {Buffer} head The first packet of the upgraded stream
116 | * @param {Number} maxPayload The maximum allowed message size
117 | * @private
118 | */
119 | setSocket (socket, head, maxPayload) {
120 | const receiver = new Receiver(
121 | this._binaryType,
122 | this._extensions,
123 | maxPayload
124 | );
125 |
126 | this._sender = new Sender(socket, this._extensions);
127 | this._receiver = receiver;
128 | this._socket = socket;
129 |
130 | receiver[kWebSocket] = this;
131 | socket[kWebSocket] = this;
132 |
133 | receiver.on('conclude', receiverOnConclude);
134 | receiver.on('drain', receiverOnDrain);
135 | receiver.on('error', receiverOnError);
136 | receiver.on('message', receiverOnMessage);
137 | receiver.on('ping', receiverOnPing);
138 | receiver.on('pong', receiverOnPong);
139 |
140 | socket.setTimeout(0);
141 | socket.setNoDelay();
142 |
143 | if (head.length > 0) socket.unshift(head);
144 |
145 | socket.on('close', socketOnClose);
146 | socket.on('data', socketOnData);
147 | socket.on('end', socketOnEnd);
148 | socket.on('error', socketOnError);
149 |
150 | this.readyState = WebSocket.OPEN;
151 | this.emit('open');
152 | }
153 |
154 | /**
155 | * Emit the `'close'` event.
156 | *
157 | * @private
158 | */
159 | emitClose () {
160 | this.readyState = WebSocket.CLOSED;
161 |
162 | if (!this._socket) {
163 | this.emit('close', this._closeCode, this._closeMessage);
164 | return;
165 | }
166 |
167 | if (this._extensions[PerMessageDeflate.extensionName]) {
168 | this._extensions[PerMessageDeflate.extensionName].cleanup();
169 | }
170 |
171 | this._receiver.removeAllListeners();
172 | this.emit('close', this._closeCode, this._closeMessage);
173 | }
174 |
175 | /**
176 | * Start a closing handshake.
177 | *
178 | * +----------+ +-----------+ +----------+
179 | * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
180 | * | +----------+ +-----------+ +----------+ |
181 | * +----------+ +-----------+ |
182 | * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
183 | * +----------+ +-----------+ |
184 | * | | | +---+ |
185 | * +------------------------+-->|fin| - - - -
186 | * | +---+ | +---+
187 | * - - - - -|fin|<---------------------+
188 | * +---+
189 | *
190 | * @param {Number} code Status code explaining why the connection is closing
191 | * @param {String} data A string explaining why the connection is closing
192 | * @public
193 | */
194 | close (code, data) {
195 | if (this.readyState === WebSocket.CLOSED) return;
196 | if (this.readyState === WebSocket.CONNECTING) {
197 | const msg = 'WebSocket was closed before the connection was established';
198 | return abortHandshake(this, this._req, msg);
199 | }
200 |
201 | if (this.readyState === WebSocket.CLOSING) {
202 | if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
203 | return;
204 | }
205 |
206 | this.readyState = WebSocket.CLOSING;
207 | this._sender.close(code, data, !this._isServer, (err) => {
208 | //
209 | // This error is handled by the `'error'` listener on the socket. We only
210 | // want to know if the close frame has been sent here.
211 | //
212 | if (err) return;
213 |
214 | this._closeFrameSent = true;
215 |
216 | if (this._socket.writable) {
217 | if (this._closeFrameReceived) this._socket.end();
218 |
219 | //
220 | // Ensure that the connection is closed even if the closing handshake
221 | // fails.
222 | //
223 | this._closeTimer = setTimeout(
224 | this._socket.destroy.bind(this._socket),
225 | closeTimeout
226 | );
227 | }
228 | });
229 | }
230 |
231 | /**
232 | * Send a ping.
233 | *
234 | * @param {*} data The data to send
235 | * @param {Boolean} mask Indicates whether or not to mask `data`
236 | * @param {Function} cb Callback which is executed when the ping is sent
237 | * @public
238 | */
239 | ping (data, mask, cb) {
240 | if (typeof data === 'function') {
241 | cb = data;
242 | data = mask = undefined;
243 | } else if (typeof mask === 'function') {
244 | cb = mask;
245 | mask = undefined;
246 | }
247 |
248 | if (this.readyState !== WebSocket.OPEN) {
249 | const err = new Error(
250 | `WebSocket is not open: readyState ${this.readyState} ` +
251 | `(${readyStates[this.readyState]})`
252 | );
253 |
254 | if (cb) return cb(err);
255 | throw err;
256 | }
257 |
258 | if (typeof data === 'number') data = data.toString();
259 | if (mask === undefined) mask = !this._isServer;
260 | this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb);
261 | }
262 |
263 | /**
264 | * Send a pong.
265 | *
266 | * @param {*} data The data to send
267 | * @param {Boolean} mask Indicates whether or not to mask `data`
268 | * @param {Function} cb Callback which is executed when the pong is sent
269 | * @public
270 | */
271 | pong (data, mask, cb) {
272 | if (typeof data === 'function') {
273 | cb = data;
274 | data = mask = undefined;
275 | } else if (typeof mask === 'function') {
276 | cb = mask;
277 | mask = undefined;
278 | }
279 |
280 | if (this.readyState !== WebSocket.OPEN) {
281 | const err = new Error(
282 | `WebSocket is not open: readyState ${this.readyState} ` +
283 | `(${readyStates[this.readyState]})`
284 | );
285 |
286 | if (cb) return cb(err);
287 | throw err;
288 | }
289 |
290 | if (typeof data === 'number') data = data.toString();
291 | if (mask === undefined) mask = !this._isServer;
292 | this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb);
293 | }
294 |
295 | /**
296 | * Send a data message.
297 | *
298 | * @param {*} data The message to send
299 | * @param {Object} options Options object
300 | * @param {Boolean} options.compress Specifies whether or not to compress `data`
301 | * @param {Boolean} options.binary Specifies whether `data` is binary or text
302 | * @param {Boolean} options.fin Specifies whether the fragment is the last one
303 | * @param {Boolean} options.mask Specifies whether or not to mask `data`
304 | * @param {Function} cb Callback which is executed when data is written out
305 | * @public
306 | */
307 | send (data, options, cb) {
308 | if (typeof options === 'function') {
309 | cb = options;
310 | options = {};
311 | }
312 |
313 | if (this.readyState !== WebSocket.OPEN) {
314 | const err = new Error(
315 | `WebSocket is not open: readyState ${this.readyState} ` +
316 | `(${readyStates[this.readyState]})`
317 | );
318 |
319 | if (cb) return cb(err);
320 | throw err;
321 | }
322 |
323 | if (typeof data === 'number') data = data.toString();
324 |
325 | const opts = Object.assign({
326 | binary: typeof data !== 'string',
327 | mask: !this._isServer,
328 | compress: true,
329 | fin: true
330 | }, options);
331 |
332 | if (!this._extensions[PerMessageDeflate.extensionName]) {
333 | opts.compress = false;
334 | }
335 |
336 | this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
337 | }
338 |
339 | /**
340 | * Forcibly close the connection.
341 | *
342 | * @public
343 | */
344 | terminate () {
345 | if (this.readyState === WebSocket.CLOSED) return;
346 | if (this.readyState === WebSocket.CONNECTING) {
347 | const msg = 'WebSocket was closed before the connection was established';
348 | return abortHandshake(this, this._req, msg);
349 | }
350 |
351 | if (this._socket) {
352 | this.readyState = WebSocket.CLOSING;
353 | this._socket.destroy();
354 | }
355 | }
356 | }
357 |
358 | readyStates.forEach((readyState, i) => {
359 | WebSocket[readyStates[i]] = i;
360 | });
361 |
362 | //
363 | // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
364 | // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
365 | //
366 | ['open', 'error', 'close', 'message'].forEach((method) => {
367 | Object.defineProperty(WebSocket.prototype, `on${method}`, {
368 | /**
369 | * Return the listener of the event.
370 | *
371 | * @return {(Function|undefined)} The event listener or `undefined`
372 | * @public
373 | */
374 | get () {
375 | const listeners = this.listeners(method);
376 | for (var i = 0; i < listeners.length; i++) {
377 | if (listeners[i]._listener) return listeners[i]._listener;
378 | }
379 | },
380 | /**
381 | * Add a listener for the event.
382 | *
383 | * @param {Function} listener The listener to add
384 | * @public
385 | */
386 | set (listener) {
387 | const listeners = this.listeners(method);
388 | for (var i = 0; i < listeners.length; i++) {
389 | //
390 | // Remove only the listeners added via `addEventListener`.
391 | //
392 | if (listeners[i]._listener) this.removeListener(method, listeners[i]);
393 | }
394 | this.addEventListener(method, listener);
395 | }
396 | });
397 | });
398 |
399 | WebSocket.prototype.addEventListener = EventTarget.addEventListener;
400 | WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
401 |
402 | module.exports = WebSocket;
403 |
404 | /**
405 | * Initialize a WebSocket client.
406 | *
407 | * @param {(String|url.Url|url.URL)} address The URL to which to connect
408 | * @param {String} protocols The subprotocols
409 | * @param {Object} options Connection options
410 | * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
411 | * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request
412 | * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
413 | * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
414 | * @private
415 | */
416 | function initAsClient (address, protocols, options) {
417 | options = Object.assign({
418 | protocolVersion: protocolVersions[1],
419 | perMessageDeflate: true
420 | }, options, {
421 | createConnection: undefined,
422 | socketPath: undefined,
423 | hostname: undefined,
424 | protocol: undefined,
425 | timeout: undefined,
426 | method: undefined,
427 | auth: undefined,
428 | host: undefined,
429 | path: undefined,
430 | port: undefined
431 | });
432 |
433 | if (protocolVersions.indexOf(options.protocolVersion) === -1) {
434 | throw new RangeError(
435 | `Unsupported protocol version: ${options.protocolVersion} ` +
436 | `(supported versions: ${protocolVersions.join(', ')})`
437 | );
438 | }
439 |
440 | this._isServer = false;
441 |
442 | var parsedUrl;
443 |
444 | if (typeof address === 'object' && address.href !== undefined) {
445 | parsedUrl = address;
446 | this.url = address.href;
447 | } else {
448 | parsedUrl = url.parse(address);
449 | this.url = address;
450 | }
451 |
452 | const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
453 |
454 | if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
455 | throw new Error(`Invalid URL: ${this.url}`);
456 | }
457 |
458 | const isSecure = parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
459 | const key = crypto.randomBytes(16).toString('base64');
460 | const httpObj = isSecure ? https : http;
461 | const path = parsedUrl.search
462 | ? `${parsedUrl.pathname || '/'}${parsedUrl.search}`
463 | : parsedUrl.pathname || '/';
464 | var perMessageDeflate;
465 |
466 | options.createConnection = isSecure ? tlsConnect : netConnect;
467 | options.port = parsedUrl.port || (isSecure ? 443 : 80);
468 | options.host = parsedUrl.hostname.startsWith('[')
469 | ? parsedUrl.hostname.slice(1, -1)
470 | : parsedUrl.hostname;
471 | options.headers = Object.assign({
472 | 'Sec-WebSocket-Version': options.protocolVersion,
473 | 'Sec-WebSocket-Key': key,
474 | 'Connection': 'Upgrade',
475 | 'Upgrade': 'websocket'
476 | }, options.headers);
477 | options.path = path;
478 |
479 | if (options.perMessageDeflate) {
480 | perMessageDeflate = new PerMessageDeflate(
481 | options.perMessageDeflate !== true ? options.perMessageDeflate : {},
482 | false
483 | );
484 | options.headers['Sec-WebSocket-Extensions'] = extension.format({
485 | [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
486 | });
487 | }
488 | if (protocols) {
489 | options.headers['Sec-WebSocket-Protocol'] = protocols;
490 | }
491 | if (options.origin) {
492 | if (options.protocolVersion < 13) {
493 | options.headers['Sec-WebSocket-Origin'] = options.origin;
494 | } else {
495 | options.headers.Origin = options.origin;
496 | }
497 | }
498 | if (parsedUrl.auth) {
499 | options.auth = parsedUrl.auth;
500 | } else if (parsedUrl.username || parsedUrl.password) {
501 | options.auth = `${parsedUrl.username}:${parsedUrl.password}`;
502 | }
503 |
504 | if (isUnixSocket) {
505 | const parts = path.split(':');
506 |
507 | if (options.agent == null && process.versions.modules < 57) {
508 | //
509 | // Setting `socketPath` in conjunction with `createConnection` without an
510 | // agent throws an error on Node.js < 8. Work around the issue by using a
511 | // different property.
512 | //
513 | options._socketPath = parts[0];
514 | } else {
515 | options.socketPath = parts[0];
516 | }
517 |
518 | options.path = parts[1];
519 | }
520 |
521 | var req = this._req = httpObj.get(options);
522 |
523 | if (options.handshakeTimeout) {
524 | req.setTimeout(
525 | options.handshakeTimeout,
526 | () => abortHandshake(this, req, 'Opening handshake has timed out')
527 | );
528 | }
529 |
530 | req.on('error', (err) => {
531 | if (this._req.aborted) return;
532 |
533 | req = this._req = null;
534 | this.readyState = WebSocket.CLOSING;
535 | this.emit('error', err);
536 | this.emitClose();
537 | });
538 |
539 | req.on('response', (res) => {
540 | if (this.emit('unexpected-response', req, res)) return;
541 |
542 | abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`);
543 | });
544 |
545 | req.on('upgrade', (res, socket, head) => {
546 | this.emit('upgrade', res);
547 |
548 | //
549 | // The user may have closed the connection from a listener of the `upgrade`
550 | // event.
551 | //
552 | if (this.readyState !== WebSocket.CONNECTING) return;
553 |
554 | req = this._req = null;
555 |
556 | const digest = crypto.createHash('sha1')
557 | .update(key + constants.GUID, 'binary')
558 | .digest('base64');
559 |
560 | if (res.headers['sec-websocket-accept'] !== digest) {
561 | abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header');
562 | return;
563 | }
564 |
565 | const serverProt = res.headers['sec-websocket-protocol'];
566 | const protList = (protocols || '').split(/, */);
567 | var protError;
568 |
569 | if (!protocols && serverProt) {
570 | protError = 'Server sent a subprotocol but none was requested';
571 | } else if (protocols && !serverProt) {
572 | protError = 'Server sent no subprotocol';
573 | } else if (serverProt && protList.indexOf(serverProt) === -1) {
574 | protError = 'Server sent an invalid subprotocol';
575 | }
576 |
577 | if (protError) {
578 | abortHandshake(this, socket, protError);
579 | return;
580 | }
581 |
582 | if (serverProt) this.protocol = serverProt;
583 |
584 | if (perMessageDeflate) {
585 | try {
586 | const extensions = extension.parse(
587 | res.headers['sec-websocket-extensions']
588 | );
589 |
590 | if (extensions[PerMessageDeflate.extensionName]) {
591 | perMessageDeflate.accept(
592 | extensions[PerMessageDeflate.extensionName]
593 | );
594 | this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
595 | }
596 | } catch (err) {
597 | abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header');
598 | return;
599 | }
600 | }
601 |
602 | this.setSocket(socket, head, 0);
603 | });
604 | }
605 |
606 | /**
607 | * Create a `net.Socket` and initiate a connection.
608 | *
609 | * @param {Object} options Connection options
610 | * @return {net.Socket} The newly created socket used to start the connection
611 | * @private
612 | */
613 | function netConnect (options) {
614 | options.path = options.socketPath || options._socketPath || undefined;
615 | return net.connect(options);
616 | }
617 |
618 | /**
619 | * Create a `tls.TLSSocket` and initiate a connection.
620 | *
621 | * @param {Object} options Connection options
622 | * @return {tls.TLSSocket} The newly created socket used to start the connection
623 | * @private
624 | */
625 | function tlsConnect (options) {
626 | options.path = options.socketPath || options._socketPath || undefined;
627 | options.servername = options.servername || options.host;
628 | return tls.connect(options);
629 | }
630 |
631 | /**
632 | * Abort the handshake and emit an error.
633 | *
634 | * @param {WebSocket} websocket The WebSocket instance
635 | * @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
636 | * socket to destroy
637 | * @param {String} message The error message
638 | * @private
639 | */
640 | function abortHandshake (websocket, stream, message) {
641 | websocket.readyState = WebSocket.CLOSING;
642 |
643 | const err = new Error(message);
644 | Error.captureStackTrace(err, abortHandshake);
645 |
646 | if (stream.setHeader) {
647 | stream.abort();
648 | stream.once('abort', websocket.emitClose.bind(websocket));
649 | websocket.emit('error', err);
650 | } else {
651 | stream.destroy(err);
652 | stream.once('error', websocket.emit.bind(websocket, 'error'));
653 | stream.once('close', websocket.emitClose.bind(websocket));
654 | }
655 | }
656 |
657 | /**
658 | * The listener of the `Receiver` `'conclude'` event.
659 | *
660 | * @param {Number} code The status code
661 | * @param {String} reason The reason for closing
662 | * @private
663 | */
664 | function receiverOnConclude (code, reason) {
665 | const websocket = this[kWebSocket];
666 |
667 | websocket._socket.removeListener('data', socketOnData);
668 | websocket._socket.resume();
669 |
670 | websocket._closeFrameReceived = true;
671 | websocket._closeMessage = reason;
672 | websocket._closeCode = code;
673 |
674 | if (code === 1005) websocket.close();
675 | else websocket.close(code, reason);
676 | }
677 |
678 | /**
679 | * The listener of the `Receiver` `'drain'` event.
680 | *
681 | * @private
682 | */
683 | function receiverOnDrain () {
684 | this[kWebSocket]._socket.resume();
685 | }
686 |
687 | /**
688 | * The listener of the `Receiver` `'error'` event.
689 | *
690 | * @param {(RangeError|Error)} err The emitted error
691 | * @private
692 | */
693 | function receiverOnError (err) {
694 | const websocket = this[kWebSocket];
695 |
696 | websocket._socket.removeListener('data', socketOnData);
697 |
698 | websocket.readyState = WebSocket.CLOSING;
699 | websocket._closeCode = err[constants.kStatusCode];
700 | websocket.emit('error', err);
701 | websocket._socket.destroy();
702 | }
703 |
704 | /**
705 | * The listener of the `Receiver` `'finish'` event.
706 | *
707 | * @private
708 | */
709 | function receiverOnFinish () {
710 | this[kWebSocket].emitClose();
711 | }
712 |
713 | /**
714 | * The listener of the `Receiver` `'message'` event.
715 | *
716 | * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
717 | * @private
718 | */
719 | function receiverOnMessage (data) {
720 | this[kWebSocket].emit('message', data);
721 | }
722 |
723 | /**
724 | * The listener of the `Receiver` `'ping'` event.
725 | *
726 | * @param {Buffer} data The data included in the ping frame
727 | * @private
728 | */
729 | function receiverOnPing (data) {
730 | const websocket = this[kWebSocket];
731 |
732 | websocket.pong(data, !websocket._isServer, constants.NOOP);
733 | websocket.emit('ping', data);
734 | }
735 |
736 | /**
737 | * The listener of the `Receiver` `'pong'` event.
738 | *
739 | * @param {Buffer} data The data included in the pong frame
740 | * @private
741 | */
742 | function receiverOnPong (data) {
743 | this[kWebSocket].emit('pong', data);
744 | }
745 |
746 | /**
747 | * The listener of the `net.Socket` `'close'` event.
748 | *
749 | * @private
750 | */
751 | function socketOnClose () {
752 | const websocket = this[kWebSocket];
753 |
754 | this.removeListener('close', socketOnClose);
755 | this.removeListener('end', socketOnEnd);
756 |
757 | websocket.readyState = WebSocket.CLOSING;
758 |
759 | //
760 | // The close frame might not have been received or the `'end'` event emitted,
761 | // for example, if the socket was destroyed due to an error. Ensure that the
762 | // `receiver` stream is closed after writing any remaining buffered data to
763 | // it. If the readable side of the socket is in flowing mode then there is no
764 | // buffered data as everything has been already written and `readable.read()`
765 | // will return `null`. If instead, the socket is paused, any possible buffered
766 | // data will be read as a single chunk and emitted synchronously in a single
767 | // `'data'` event.
768 | //
769 | websocket._socket.read();
770 | websocket._receiver.end();
771 |
772 | this.removeListener('data', socketOnData);
773 | this[kWebSocket] = undefined;
774 |
775 | clearTimeout(websocket._closeTimer);
776 |
777 | if (
778 | websocket._receiver._writableState.finished ||
779 | websocket._receiver._writableState.errorEmitted
780 | ) {
781 | websocket.emitClose();
782 | } else {
783 | websocket._receiver.on('error', receiverOnFinish);
784 | websocket._receiver.on('finish', receiverOnFinish);
785 | }
786 | }
787 |
788 | /**
789 | * The listener of the `net.Socket` `'data'` event.
790 | *
791 | * @param {Buffer} chunk A chunk of data
792 | * @private
793 | */
794 | function socketOnData (chunk) {
795 | if (!this[kWebSocket]._receiver.write(chunk)) {
796 | this.pause();
797 | }
798 | }
799 |
800 | /**
801 | * The listener of the `net.Socket` `'end'` event.
802 | *
803 | * @private
804 | */
805 | function socketOnEnd () {
806 | const websocket = this[kWebSocket];
807 |
808 | websocket.readyState = WebSocket.CLOSING;
809 | websocket._receiver.end();
810 | this.end();
811 | }
812 |
813 | /**
814 | * The listener of the `net.Socket` `'error'` event.
815 | *
816 | * @private
817 | */
818 | function socketOnError () {
819 | const websocket = this[kWebSocket];
820 |
821 | this.removeListener('error', socketOnError);
822 | this.on('error', constants.NOOP);
823 |
824 | if (websocket) {
825 | websocket.readyState = WebSocket.CLOSING;
826 | this.destroy();
827 | }
828 | }
829 |
--------------------------------------------------------------------------------