mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-14 02:16:02 +02:00
Merge pull request #5594 from thoys/20638
CR for Job #20638 - Scripting: WebSocket Implementation
This commit is contained in:
commit
e9517ef929
11 changed files with 600 additions and 18 deletions
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME assignment-client)
|
||||
|
||||
setup_hifi_project(Core Gui Network Script Widgets)
|
||||
setup_hifi_project(Core Gui Network Script Widgets WebSockets)
|
||||
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <SoundCache.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include <WebSocketServerClass.h>
|
||||
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
|
||||
#include "avatars/ScriptableAvatar.h"
|
||||
|
@ -180,10 +181,17 @@ void Agent::run() {
|
|||
// register ourselves to the script engine
|
||||
_scriptEngine.registerGlobalObject("Agent", this);
|
||||
|
||||
if (!_payload.isEmpty()) {
|
||||
_scriptEngine.setParentURL(_payload);
|
||||
}
|
||||
|
||||
_scriptEngine.init(); // must be done before we set up the viewers
|
||||
|
||||
_scriptEngine.registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
QScriptValue webSocketServerConstructorValue = _scriptEngine.newFunction(WebSocketServerClass::constructor);
|
||||
_scriptEngine.globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue);
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
_scriptEngine.registerGlobalObject("EntityViewer", &_entityViewer);
|
||||
|
|
|
@ -11,17 +11,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var test = function(name, func) {
|
||||
test = function(name, func, timeout) {
|
||||
print("Running test: " + name);
|
||||
|
||||
var unitTest = new UnitTest(name, func);
|
||||
|
||||
try {
|
||||
unitTest.run();
|
||||
var unitTest = new UnitTest(name, func, timeout);
|
||||
unitTest.run(function(unitTest) {
|
||||
print(" Success: " + unitTest.numAssertions + " assertions passed");
|
||||
} catch (error) {
|
||||
}, function(unitTest, error) {
|
||||
print(" Failure: " + error.name + " " + error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
AssertionException = function(expected, actual, message) {
|
||||
|
@ -36,13 +33,86 @@ UnthrownException = function(message) {
|
|||
this.name = 'UnthrownException';
|
||||
};
|
||||
|
||||
UnitTest = function(name, func) {
|
||||
this.numAssertions = 0;
|
||||
this.func = func;
|
||||
TimeoutException = function() {
|
||||
print("Creating exception");
|
||||
this.message = "UnitTest timed out\n";
|
||||
this.name = 'TimeoutException';
|
||||
};
|
||||
|
||||
UnitTest.prototype.run = function() {
|
||||
this.func();
|
||||
SequentialUnitTester = function() {
|
||||
this.tests = [];
|
||||
this.testIndex = -1;
|
||||
};
|
||||
|
||||
SequentialUnitTester.prototype.addTest = function(name, func, timeout) {
|
||||
var _this = this;
|
||||
this.tests.push(function() {
|
||||
print("Running test: " + name);
|
||||
var unitTest = new UnitTest(name, func, timeout);
|
||||
unitTest.run(function(unitTest) {
|
||||
print(" Success: " + unitTest.numAssertions + " assertions passed");
|
||||
_this._nextTest();
|
||||
}, function(unitTest, error) {
|
||||
print(" Failure: " + error.name + " " + error.message);
|
||||
_this._nextTest();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SequentialUnitTester.prototype._nextTest = function() {
|
||||
this.testIndex++;
|
||||
if (this.testIndex < this.tests.length) {
|
||||
this.tests[this.testIndex]();
|
||||
return;
|
||||
}
|
||||
print("Completed all UnitTests");
|
||||
};
|
||||
|
||||
SequentialUnitTester.prototype.run = function() {
|
||||
this._nextTest();
|
||||
};
|
||||
|
||||
UnitTest = function(name, func, timeout) {
|
||||
this.numAssertions = 0;
|
||||
this.func = func;
|
||||
this.timeout = timeout;
|
||||
};
|
||||
|
||||
UnitTest.prototype.run = function(successCallback, failureCallback) {
|
||||
var _this = this;
|
||||
this.successCallback = successCallback;
|
||||
this.failureCallback = failureCallback;
|
||||
if (this.timeout !== undefined) {
|
||||
this.timeoutTimer = Script.setTimeout(function() {
|
||||
_this.failureCallback(this, new TimeoutException());
|
||||
}, this.timeout);
|
||||
}
|
||||
try {
|
||||
this.func();
|
||||
if (this.timeout === undefined) {
|
||||
successCallback(this);
|
||||
}
|
||||
} catch (exception) {
|
||||
this.handleException(exception);
|
||||
}
|
||||
};
|
||||
|
||||
UnitTest.prototype.registerCallbackFunction = function(func) {
|
||||
var _this = this;
|
||||
return function(one, two, three, four, five, six) {
|
||||
try {
|
||||
func(one, two, three, four, five, six);
|
||||
} catch (exception) {
|
||||
_this.handleException(exception);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
UnitTest.prototype.handleException = function(exception) {
|
||||
if (this.timeout !== undefined) {
|
||||
Script.clearTimeout(this.timeoutTimer);
|
||||
}
|
||||
this.failureCallback(this, exception);
|
||||
};
|
||||
|
||||
UnitTest.prototype.assertNotEquals = function(expected, actual, message) {
|
||||
|
@ -83,7 +153,7 @@ UnitTest.prototype.assertNull = function(value, message) {
|
|||
UnitTest.prototype.arrayEqual = function(array1, array2, message) {
|
||||
this.numAssertions++;
|
||||
if (array1.length !== array2.length) {
|
||||
throw new AssertionException(array1.length , array2.length , message);
|
||||
throw new AssertionException(array1.length, array2.length , message);
|
||||
}
|
||||
for (var i = 0; i < array1.length; ++i) {
|
||||
if (array1[i] !== array2[i]) {
|
||||
|
@ -101,4 +171,11 @@ UnitTest.prototype.raises = function(func, message) {
|
|||
}
|
||||
|
||||
throw new UnthrownException(message);
|
||||
}
|
||||
}
|
||||
|
||||
UnitTest.prototype.done = function() {
|
||||
if (this.timeout !== undefined) {
|
||||
Script.clearTimeout(this.timeoutTimer);
|
||||
this.successCallback(this);
|
||||
}
|
||||
}
|
||||
|
|
102
examples/utilities/diagnostics/testWebSocket.js
Normal file
102
examples/utilities/diagnostics/testWebSocket.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// testWebSocket.js
|
||||
// examples
|
||||
//
|
||||
// Created by Thijs Wenker on 8/18/15
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// WebSocket and WebSocketServer Tests
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
Script.include("../../libraries/unitTest.js");
|
||||
|
||||
// We set the unit testing timeout to 1000 milliseconds by default. Please increase if the test fails due to a slow connection.
|
||||
const UNITTEST_TIMEOUT = 1000;
|
||||
const WEBSOCKET_PING_URL = "ws://echo.websocket.org";
|
||||
// Please do not register the following domain + gTLD:
|
||||
const WEBSOCKET_INVALID_URL = "ws://thisisnotavaliddomainname.invalid";
|
||||
const TEST_MESSAGE = "This is a test message.";
|
||||
|
||||
var unitTests = new SequentialUnitTester();
|
||||
|
||||
unitTests.addTest("Test default WebSocket values", function(finished) {
|
||||
var _this = this;
|
||||
var webSocket = new WebSocket(WEBSOCKET_PING_URL);
|
||||
|
||||
webSocket.onmessage = this.registerCallbackFunction(function(event) {
|
||||
_this.assertEquals(TEST_MESSAGE, event.data, "event.data should be '" + TEST_MESSAGE + "'");
|
||||
webSocket.close();
|
||||
});
|
||||
webSocket.onopen = this.registerCallbackFunction(function(event) {
|
||||
_this.assertEquals(webSocket.OPEN, webSocket.readyState, "readyState should be OPEN");
|
||||
webSocket.send(TEST_MESSAGE);
|
||||
});
|
||||
webSocket.onclose = this.registerCallbackFunction(function(event) {
|
||||
_this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED");
|
||||
_this.done();
|
||||
});
|
||||
this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING");
|
||||
this.assertEquals("blob", webSocket.binaryType, "binaryType should be 'blob'");
|
||||
this.assertEquals(0, webSocket.bufferedAmount, "bufferedAmount should be 0");
|
||||
this.assertEquals("", webSocket.extensions, "extensions should be an empty string by default");
|
||||
this.assertEquals("", webSocket.protocol, "protocol should be an empty string by default");
|
||||
this.assertEquals(WEBSOCKET_PING_URL, webSocket.url, "url should be '" + WEBSOCKET_PING_URL + "'");
|
||||
}, UNITTEST_TIMEOUT);
|
||||
|
||||
unitTests.addTest("Test WebSocket invalid URL", function(finished) {
|
||||
var _this = this;
|
||||
var webSocket = new WebSocket(WEBSOCKET_INVALID_URL);
|
||||
var hadError = false;
|
||||
webSocket.onerror = this.registerCallbackFunction(function() {
|
||||
hadError = true;
|
||||
_this.done();
|
||||
});
|
||||
webSocket.onclose = this.registerCallbackFunction(function(event) {
|
||||
_this.assertEquals(webSocket.CLOSED, webSocket.readyState, "readyState should be CLOSED");
|
||||
});
|
||||
this.assertEquals(webSocket.CONNECTING, webSocket.readyState, "readyState should be CONNECTING");
|
||||
this.assertEquals(WEBSOCKET_INVALID_URL, webSocket.url, "url should be '" + WEBSOCKET_INVALID_URL + "'");
|
||||
}, UNITTEST_TIMEOUT);
|
||||
|
||||
if (this.WebSocketServer === undefined) {
|
||||
print("Skipping WebSocketServer tests.");
|
||||
} else {
|
||||
unitTests.addTest("Test WebSocketServer with three clients", function(finished) {
|
||||
var _this = this;
|
||||
const NUMBER_OF_CLIENTS = 3;
|
||||
var connectedClients = 0;
|
||||
var respondedClients = 0;
|
||||
var webSocketServer = new WebSocketServer();
|
||||
_this.assertEquals(true, webSocketServer.listening, "listening should be true");
|
||||
webSocketServer.newConnection.connect(this.registerCallbackFunction(function(newClient) {
|
||||
connectedClients++;
|
||||
newClient.onmessage = _this.registerCallbackFunction(function(event) {
|
||||
var data = JSON.parse(event.data);
|
||||
_this.assertEquals(TEST_MESSAGE, data.message, "data.message should be '" + TEST_MESSAGE + "'");
|
||||
respondedClients++;
|
||||
if (respondedClients === NUMBER_OF_CLIENTS) {
|
||||
webSocketServer.close();
|
||||
_this.assertEquals(false, webSocketServer.listening, "listening should be false");
|
||||
_this.done();
|
||||
}
|
||||
});
|
||||
newClient.send(JSON.stringify({message: TEST_MESSAGE, client: connectedClients}));
|
||||
}));
|
||||
var newSocket1 = new WebSocket(webSocketServer.url);
|
||||
newSocket1.onmessage = this.registerCallbackFunction(function(event) {
|
||||
newSocket1.send(event.data);
|
||||
});
|
||||
var newSocket2 = new WebSocket(webSocketServer.url);
|
||||
newSocket2.onmessage = this.registerCallbackFunction(function(event) {
|
||||
newSocket2.send(event.data);
|
||||
});
|
||||
var newSocket3 = new WebSocket(webSocketServer.url);
|
||||
newSocket3.onmessage = this.registerCallbackFunction(function(event) {
|
||||
newSocket3.send(event.data);
|
||||
});
|
||||
}, UNITTEST_TIMEOUT);
|
||||
}
|
||||
|
||||
unitTests.run();
|
|
@ -40,7 +40,7 @@ else ()
|
|||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||
endif ()
|
||||
|
||||
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets)
|
||||
find_package(Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg WebKitWidgets WebSockets)
|
||||
|
||||
# grab the ui files in resources/ui
|
||||
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
|
||||
|
|
|
@ -3,7 +3,7 @@ set(TARGET_NAME script-engine)
|
|||
setup_memory_debugger()
|
||||
|
||||
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
|
||||
setup_hifi_library(Gui Network Script Widgets)
|
||||
setup_hifi_library(Gui Network Script WebSockets Widgets)
|
||||
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "ScriptEngine.h"
|
||||
#include "TypedArrays.h"
|
||||
#include "XMLHttpRequestClass.h"
|
||||
#include "WebSocketClass.h"
|
||||
|
||||
#include "SceneScriptingInterface.h"
|
||||
|
||||
|
@ -343,6 +344,9 @@ void ScriptEngine::init() {
|
|||
QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor);
|
||||
globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);
|
||||
|
||||
QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor);
|
||||
globalObject().setProperty("WebSocket", webSocketConstructorValue);
|
||||
|
||||
QScriptValue printConstructorValue = newFunction(debugPrint);
|
||||
globalObject().setProperty("print", printConstructorValue);
|
||||
|
||||
|
@ -353,6 +357,9 @@ void ScriptEngine::init() {
|
|||
qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue);
|
||||
qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue);
|
||||
qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue);
|
||||
qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue);
|
||||
qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue);
|
||||
qScriptRegisterMetaType(this, wscReadyStateToScriptValue, wscReadyStateFromScriptValue);
|
||||
|
||||
registerGlobalObject("Script", this);
|
||||
registerGlobalObject("Audio", &AudioScriptingInterface::getInstance());
|
||||
|
|
127
libraries/script-engine/src/WebSocketClass.cpp
Normal file
127
libraries/script-engine/src/WebSocketClass.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// WebSocketClass.cpp
|
||||
// libraries/script-engine/src/
|
||||
//
|
||||
// Created by Thijs Wenker on 8/4/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// This class is an implementation of the WebSocket object for scripting use. It provides a near-complete implementation
|
||||
// of the class described in the Mozilla docs: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "WebSocketClass.h"
|
||||
|
||||
WebSocketClass::WebSocketClass(QScriptEngine* engine, QString url) :
|
||||
_engine(engine),
|
||||
_webSocket(new QWebSocket())
|
||||
{
|
||||
initialize();
|
||||
_webSocket->open(url);
|
||||
}
|
||||
|
||||
WebSocketClass::WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket) :
|
||||
_engine(engine),
|
||||
_webSocket(qWebSocket)
|
||||
{
|
||||
initialize();
|
||||
}
|
||||
|
||||
void WebSocketClass::initialize() {
|
||||
connect(_webSocket, &QWebSocket::disconnected, this, &WebSocketClass::handleOnClose);
|
||||
connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage);
|
||||
connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen);
|
||||
connect(_webSocket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this,
|
||||
&WebSocketClass::handleOnError);
|
||||
_binaryType = QStringLiteral("blob");
|
||||
}
|
||||
|
||||
QScriptValue WebSocketClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
QString url;
|
||||
if (context->argumentCount() > 0) {
|
||||
url = context->argument(0).toString();
|
||||
}
|
||||
return engine->newQObject(new WebSocketClass(engine, url), QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
WebSocketClass::~WebSocketClass() {
|
||||
_webSocket->deleteLater();
|
||||
}
|
||||
|
||||
void WebSocketClass::send(QScriptValue message) {
|
||||
_webSocket->sendTextMessage(message.toString());
|
||||
}
|
||||
|
||||
void WebSocketClass::close() {
|
||||
this->close(QWebSocketProtocol::CloseCodeNormal);
|
||||
}
|
||||
|
||||
void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode) {
|
||||
this->close(closeCode, QStringLiteral(""));
|
||||
}
|
||||
|
||||
void WebSocketClass::close(QWebSocketProtocol::CloseCode closeCode, QString reason) {
|
||||
_webSocket->close(closeCode, reason);
|
||||
}
|
||||
|
||||
void WebSocketClass::handleOnClose() {
|
||||
bool hasError = (_webSocket->error() != QAbstractSocket::UnknownSocketError);
|
||||
if (_onCloseEvent.isFunction()) {
|
||||
QScriptValueList args;
|
||||
QScriptValue arg = _engine->newObject();
|
||||
arg.setProperty("code", hasError ? QWebSocketProtocol::CloseCodeAbnormalDisconnection : _webSocket->closeCode());
|
||||
arg.setProperty("reason", _webSocket->closeReason());
|
||||
arg.setProperty("wasClean", !hasError);
|
||||
args << arg;
|
||||
_onCloseEvent.call(QScriptValue(), args);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClass::handleOnError(QAbstractSocket::SocketError error) {
|
||||
if (_onErrorEvent.isFunction()) {
|
||||
_onErrorEvent.call();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClass::handleOnMessage(const QString& message) {
|
||||
if (_onMessageEvent.isFunction()) {
|
||||
QScriptValueList args;
|
||||
QScriptValue arg = _engine->newObject();
|
||||
arg.setProperty("data", message);
|
||||
args << arg;
|
||||
_onMessageEvent.call(QScriptValue(), args);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClass::handleOnOpen() {
|
||||
if (_onOpenEvent.isFunction()) {
|
||||
_onOpenEvent.call();
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode &closeCode) {
|
||||
return closeCode;
|
||||
}
|
||||
|
||||
void qWSCloseCodeFromScriptValue(const QScriptValue &object, QWebSocketProtocol::CloseCode &closeCode) {
|
||||
closeCode = (QWebSocketProtocol::CloseCode)object.toUInt16();
|
||||
}
|
||||
|
||||
QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in) {
|
||||
return engine->newQObject(in, QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out) {
|
||||
out = qobject_cast<WebSocketClass*>(object.toQObject());
|
||||
}
|
||||
|
||||
QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState) {
|
||||
return readyState;
|
||||
}
|
||||
|
||||
void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState) {
|
||||
readyState = (WebSocketClass::ReadyState)object.toUInt16();
|
||||
}
|
140
libraries/script-engine/src/WebSocketClass.h
Normal file
140
libraries/script-engine/src/WebSocketClass.h
Normal file
|
@ -0,0 +1,140 @@
|
|||
//
|
||||
// WebSocketClass.h
|
||||
// libraries/script-engine/src/
|
||||
//
|
||||
// Created by Thijs Wenker on 8/4/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_WebSocketClass_h
|
||||
#define hifi_WebSocketClass_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QScriptEngine>
|
||||
#include <QWebSocket>
|
||||
|
||||
class WebSocketClass : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString binaryType READ getBinaryType WRITE setBinaryType)
|
||||
Q_PROPERTY(ulong bufferedAmount READ getBufferedAmount)
|
||||
Q_PROPERTY(QString extensions READ getExtensions)
|
||||
|
||||
Q_PROPERTY(QScriptValue onclose READ getOnClose WRITE setOnClose)
|
||||
Q_PROPERTY(QScriptValue onerror READ getOnError WRITE setOnError)
|
||||
Q_PROPERTY(QScriptValue onmessage READ getOnMessage WRITE setOnMessage)
|
||||
Q_PROPERTY(QScriptValue onopen READ getOnOpen WRITE setOnOpen)
|
||||
|
||||
Q_PROPERTY(QString protocol READ getProtocol)
|
||||
Q_PROPERTY(WebSocketClass::ReadyState readyState READ getReadyState)
|
||||
Q_PROPERTY(QString url READ getURL)
|
||||
|
||||
Q_PROPERTY(WebSocketClass::ReadyState CONNECTING READ getConnecting CONSTANT)
|
||||
Q_PROPERTY(WebSocketClass::ReadyState OPEN READ getOpen CONSTANT)
|
||||
Q_PROPERTY(WebSocketClass::ReadyState CLOSING READ getClosing CONSTANT)
|
||||
Q_PROPERTY(WebSocketClass::ReadyState CLOSED READ getClosed CONSTANT)
|
||||
|
||||
public:
|
||||
WebSocketClass(QScriptEngine* engine, QString url);
|
||||
WebSocketClass(QScriptEngine* engine, QWebSocket* qWebSocket);
|
||||
~WebSocketClass();
|
||||
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
enum ReadyState {
|
||||
CONNECTING = 0,
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED
|
||||
};
|
||||
|
||||
QWebSocket* getWebSocket() { return _webSocket; }
|
||||
|
||||
ReadyState getConnecting() const { return CONNECTING; };
|
||||
ReadyState getOpen() const { return OPEN; };
|
||||
ReadyState getClosing() const { return CLOSING; };
|
||||
ReadyState getClosed() const { return CLOSED; };
|
||||
|
||||
void setBinaryType(QString binaryType) { _binaryType = binaryType; }
|
||||
QString getBinaryType() { return _binaryType; }
|
||||
|
||||
// extensions is a empty string until supported in QT WebSockets
|
||||
QString getExtensions() { return QString(); }
|
||||
|
||||
// protocol is a empty string until supported in QT WebSockets
|
||||
QString getProtocol() { return QString(); }
|
||||
|
||||
ulong getBufferedAmount() { return 0; }
|
||||
|
||||
QString getURL() { return _webSocket->requestUrl().toDisplayString(); }
|
||||
|
||||
ReadyState getReadyState() {
|
||||
switch (_webSocket->state()) {
|
||||
case QAbstractSocket::SocketState::HostLookupState:
|
||||
case QAbstractSocket::SocketState::ConnectingState:
|
||||
return CONNECTING;
|
||||
case QAbstractSocket::SocketState::ConnectedState:
|
||||
case QAbstractSocket::SocketState::BoundState:
|
||||
case QAbstractSocket::SocketState::ListeningState:
|
||||
return OPEN;
|
||||
case QAbstractSocket::SocketState::ClosingState:
|
||||
return CLOSING;
|
||||
}
|
||||
return CLOSED;
|
||||
}
|
||||
|
||||
void setOnClose(QScriptValue eventFunction) { _onCloseEvent = eventFunction; }
|
||||
QScriptValue getOnClose() { return _onCloseEvent; }
|
||||
|
||||
void setOnError(QScriptValue eventFunction) { _onErrorEvent = eventFunction; }
|
||||
QScriptValue getOnError() { return _onErrorEvent; }
|
||||
|
||||
void setOnMessage(QScriptValue eventFunction) { _onMessageEvent = eventFunction; }
|
||||
QScriptValue getOnMessage() { return _onMessageEvent; }
|
||||
|
||||
void setOnOpen(QScriptValue eventFunction) { _onOpenEvent = eventFunction; }
|
||||
QScriptValue getOnOpen() { return _onOpenEvent; }
|
||||
|
||||
public slots:
|
||||
void send(QScriptValue message);
|
||||
|
||||
void close();
|
||||
void close(QWebSocketProtocol::CloseCode closeCode);
|
||||
void close(QWebSocketProtocol::CloseCode closeCode, QString reason);
|
||||
|
||||
private:
|
||||
QWebSocket* _webSocket;
|
||||
QScriptEngine* _engine;
|
||||
|
||||
QScriptValue _onCloseEvent;
|
||||
QScriptValue _onErrorEvent;
|
||||
QScriptValue _onMessageEvent;
|
||||
QScriptValue _onOpenEvent;
|
||||
|
||||
QString _binaryType;
|
||||
|
||||
void initialize();
|
||||
|
||||
private slots:
|
||||
void handleOnClose();
|
||||
void handleOnError(QAbstractSocket::SocketError error);
|
||||
void handleOnMessage(const QString& message);
|
||||
void handleOnOpen();
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QWebSocketProtocol::CloseCode);
|
||||
Q_DECLARE_METATYPE(WebSocketClass::ReadyState);
|
||||
|
||||
QScriptValue qWSCloseCodeToScriptValue(QScriptEngine* engine, const QWebSocketProtocol::CloseCode& closeCode);
|
||||
void qWSCloseCodeFromScriptValue(const QScriptValue& object, QWebSocketProtocol::CloseCode& closeCode);
|
||||
|
||||
QScriptValue webSocketToScriptValue(QScriptEngine* engine, WebSocketClass* const &in);
|
||||
void webSocketFromScriptValue(const QScriptValue &object, WebSocketClass* &out);
|
||||
|
||||
QScriptValue wscReadyStateToScriptValue(QScriptEngine* engine, const WebSocketClass::ReadyState& readyState);
|
||||
void wscReadyStateFromScriptValue(const QScriptValue& object, WebSocketClass::ReadyState& readyState);
|
||||
|
||||
#endif // hifi_WebSocketClass_h
|
69
libraries/script-engine/src/WebSocketServerClass.cpp
Normal file
69
libraries/script-engine/src/WebSocketServerClass.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// WebSocketServerClass.cpp
|
||||
// libraries/script-engine/src/
|
||||
//
|
||||
// Created by Thijs Wenker on 8/10/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Making WebSocketServer accessible through scripting.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "WebSocketServerClass.h"
|
||||
|
||||
WebSocketServerClass::WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port) :
|
||||
_engine(engine),
|
||||
_webSocketServer(serverName, QWebSocketServer::SslMode::NonSecureMode)
|
||||
{
|
||||
if (_webSocketServer.listen(QHostAddress::Any, port)) {
|
||||
connect(&_webSocketServer, &QWebSocketServer::newConnection, this, &WebSocketServerClass::onNewConnection);
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue WebSocketServerClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||
// the serverName is used in handshakes
|
||||
QString serverName = QStringLiteral("HighFidelity - Scripted WebSocket Listener");
|
||||
// port 0 will auto-assign a free port
|
||||
quint16 port = 0;
|
||||
QScriptValue callee = context->callee();
|
||||
if (context->argumentCount() > 0) {
|
||||
QScriptValue options = context->argument(0);
|
||||
QScriptValue portOption = options.property(QStringLiteral("port"));
|
||||
if (portOption.isValid() && portOption.isNumber()) {
|
||||
port = portOption.toNumber();
|
||||
}
|
||||
QScriptValue serverNameOption = options.property(QStringLiteral("serverName"));
|
||||
if (serverNameOption.isValid() && serverNameOption.isString()) {
|
||||
serverName = serverNameOption.toString();
|
||||
}
|
||||
}
|
||||
return engine->newQObject(new WebSocketServerClass(engine, serverName, port), QScriptEngine::ScriptOwnership);
|
||||
}
|
||||
|
||||
WebSocketServerClass::~WebSocketServerClass() {
|
||||
if (_webSocketServer.isListening()) {
|
||||
close();
|
||||
}
|
||||
_clients.empty();
|
||||
}
|
||||
|
||||
void WebSocketServerClass::onNewConnection() {
|
||||
WebSocketClass* newClient = new WebSocketClass(_engine, _webSocketServer.nextPendingConnection());
|
||||
_clients << newClient;
|
||||
connect(newClient->getWebSocket(), &QWebSocket::disconnected, [newClient, this]() {
|
||||
_clients.removeOne(newClient);
|
||||
});
|
||||
emit newConnection(newClient);
|
||||
}
|
||||
|
||||
void WebSocketServerClass::close() {
|
||||
foreach(WebSocketClass* client, _clients) {
|
||||
if (client->getReadyState() != WebSocketClass::ReadyState::CLOSED) {
|
||||
client->close(QWebSocketProtocol::CloseCode::CloseCodeGoingAway, "Server closing.");
|
||||
}
|
||||
}
|
||||
_webSocketServer.close();
|
||||
}
|
52
libraries/script-engine/src/WebSocketServerClass.h
Normal file
52
libraries/script-engine/src/WebSocketServerClass.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// WebSocketServerClass.h
|
||||
// libraries/script-engine/src/
|
||||
//
|
||||
// Created by Thijs Wenker on 8/10/15.
|
||||
// Copyright (c) 2015 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_WebSocketServerClass_h
|
||||
#define hifi_WebSocketServerClass_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QScriptEngine>
|
||||
#include <QWebSocketServer>
|
||||
#include "WebSocketClass.h"
|
||||
|
||||
class WebSocketServerClass : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString url READ getURL)
|
||||
Q_PROPERTY(quint16 port READ getPort)
|
||||
Q_PROPERTY(bool listening READ isListening)
|
||||
|
||||
public:
|
||||
WebSocketServerClass(QScriptEngine* engine, const QString& serverName, const quint16 port);
|
||||
~WebSocketServerClass();
|
||||
|
||||
QString getURL() { return _webSocketServer.serverUrl().toDisplayString(); }
|
||||
quint16 getPort() { return _webSocketServer.serverPort(); }
|
||||
bool isListening() { return _webSocketServer.isListening(); }
|
||||
|
||||
static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
public slots:
|
||||
void close();
|
||||
|
||||
private:
|
||||
QWebSocketServer _webSocketServer;
|
||||
QScriptEngine* _engine;
|
||||
QList<WebSocketClass*> _clients;
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
|
||||
signals:
|
||||
void newConnection(WebSocketClass* client);
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_WebSocketServerClass_h
|
Loading…
Reference in a new issue