// // XMLHttpRequestClass.h // libraries/script-engine/src/ // // Created by Ryan Huffman on 5/2/14. // Copyright (c) 2014 High Fidelity, Inc. All rights reserved. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // /// @addtogroup ScriptEngine /// @{ #ifndef hifi_XMLHttpRequestClass_h #define hifi_XMLHttpRequestClass_h #include #include #include #include #include #include "ScriptEngine.h" #include "ScriptValue.h" class ScriptContext; /* XMlHttpRequest object XMlHttpRequest.objectName string XMlHttpRequest.response undefined XMlHttpRequest.responseText string XMlHttpRequest.responseType string XMlHttpRequest.status number XMlHttpRequest.statusText string XMlHttpRequest.readyState number XMlHttpRequest.errorCode number XMlHttpRequest.timeout number XMlHttpRequest.UNSENT number XMlHttpRequest.OPENED number XMlHttpRequest.HEADERS_RECEIVED number XMlHttpRequest.LOADING number XMlHttpRequest.DONE number XMlHttpRequest.ontimeout object XMlHttpRequest.onreadystatechange object XMlHttpRequest.destroyed(QObject*) function XMlHttpRequest.destroyed() function XMlHttpRequest.objectNameChanged(QString) function XMlHttpRequest.deleteLater() function XMlHttpRequest.requestComplete() function XMlHttpRequest.abort() function XMlHttpRequest.setRequestHeader(QString,QString) function XMlHttpRequest.open(QString,QString,bool,QString,QString) function XMlHttpRequest.open(QString,QString,bool,QString) function XMlHttpRequest.open(QString,QString,bool) function XMlHttpRequest.open(QString,QString) function XMlHttpRequest.send() function XMlHttpRequest.send(ScriptValue) function XMlHttpRequest.getAllResponseHeaders() function XMlHttpRequest.getResponseHeader(QString) function */ /*@jsdoc * Provides a means to interact with web servers. It is a near-complete implementation of the XMLHttpRequest API described in * the Mozilla docs: * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest. * *

Create using new XMLHttpRequest(...).

* * @class XMLHttpRequest * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {*} response - The response data. * Read-only. * @property {string} responseText - The response data as text. * Read-only. * @property {string} responseType - The response type required or received (e.g., "text", "json", * "arraybuffer", ...). * @property {number} status - The HTTP response status * code (100599). * Read-only. * @property {string} statusText - The HTTP response status text. * Read-only. * @property {XMLHttpRequest.ReadyState} readyState - The status of the request. * Read-only. * @property {XMLHttpRequest.NetworkError} errorCode - The network result of the request: including, 0 (NoError) * if there was no error, 4 (TimeoutError) if the request timed out. * Read-only. * @property {number} timeout - The time a request can take before timing out, in ms. * * @property {XMLHttpRequest.ReadyState} UNSENT - Request has been created; {@link XMLHttpRequest.open} not called yet. * Read-only. * @property {XMLHttpRequest.ReadyState} OPENED - {@link XMLHttpRequest.open} has been called. * Read-only. * @property {XMLHttpRequest.ReadyState} HEADERS_RECEIVED - {@link XMLHttpRequest.send} has been called; headers and status * are available. * Read-only. * @property {XMLHttpRequest.ReadyState} LOADING - Downloading; {@link XMLHttpRequest|XMLHttpRequest.responseText} has partial * data. * Read-only. * @property {XMLHttpRequest.ReadyState} DONE - Operation complete. * Read-only. * * @property {XMLHttpRequest~onTimeoutCallback} ontimeout - Function called when the request times out. *

Note: This is called in addition to any function set for onreadystatechange.

* @property {XMLHttpRequest~onReadyStateChangeCallback} onreadystatechange - Function called when the request's ready state * changes. * * @example Get a web page's HTML. * var URL = "https://www.highfidelity.com/"; * * var req = new XMLHttpRequest(); * req.onreadystatechange = function () { * if (req.readyState === req.DONE) { * if (req.status === 200) { * print("Success"); * print("Content type:", req.getResponseHeader("content-type")); * print("Content:", req.responseText.slice(0, 100), "..."); * * } else { * print("Error", req.status, req.statusText); * } * * req = null; * } * }; * * req.open("GET", URL); * req.send(); * * @example Get a web page's HTML — alternative method. * var URL = "https://www.highfidelity.com/"; * * var req = new XMLHttpRequest(); * req.requestComplete.connect(function () { * if (req.status === 200) { * print("Success"); * print("Content type:", req.getResponseHeader("content-type")); * print("Content:", req.responseText.slice(0, 100), "..."); * * } else { * print("Error", req.status, req.statusText); * } * * req = null; * }); * * req.open("GET", URL); * req.send(); */ /// Provides the XMLHttpRequest scripting interface class XMLHttpRequestClass : public QObject { Q_OBJECT Q_PROPERTY(ScriptValue response READ getResponse) Q_PROPERTY(ScriptValue responseText READ getResponseText) Q_PROPERTY(QString responseType READ getResponseType WRITE setResponseType) Q_PROPERTY(ScriptValue status READ getStatus) Q_PROPERTY(QString statusText READ getStatusText) Q_PROPERTY(ScriptValue readyState READ getReadyState) Q_PROPERTY(ScriptValue errorCode READ getError) Q_PROPERTY(int timeout READ getTimeout WRITE setTimeout) Q_PROPERTY(int UNSENT READ getUnsent) Q_PROPERTY(int OPENED READ getOpened) Q_PROPERTY(int HEADERS_RECEIVED READ getHeadersReceived) Q_PROPERTY(int LOADING READ getLoading) Q_PROPERTY(int DONE READ getDone) // Callbacks Q_PROPERTY(ScriptValue ontimeout READ getOnTimeout WRITE setOnTimeout) Q_PROPERTY(ScriptValue onreadystatechange READ getOnReadyStateChange WRITE setOnReadyStateChange) public: XMLHttpRequestClass(ScriptEngine* engine); ~XMLHttpRequestClass(); static const int MAXIMUM_REDIRECTS = 5; /*@jsdoc *

The state of an XMLHttpRequest.

* * * * * * * * * * * *
ValueNameDescription
0UNSENTRequest has been created; {@link XMLHttpRequest.open} not called * yet.
1OPENED{@link XMLHttpRequest.open} has been called.
2HEADERS_RECEIVED{@link XMLHttpRequest.send} has been called; headers and * status are available.
3LOADINGDownloading; {@link XMLHttpRequest|XMLHttpRequest.responseText} has * partial data.
4DONEOperation complete.
* @typedef {number} XMLHttpRequest.ReadyState */ enum ReadyState { UNSENT = 0, OPENED, HEADERS_RECEIVED, LOADING, DONE }; int getUnsent() const { return UNSENT; }; int getOpened() const { return OPENED; }; int getHeadersReceived() const { return HEADERS_RECEIVED; }; int getLoading() const { return LOADING; }; int getDone() const { return DONE; }; static ScriptValue constructor(ScriptContext* context, ScriptEngine* engine); int getTimeout() const { return _timeout; } void setTimeout(int timeout) { _timeout = timeout; } ScriptValue getResponse() const { return _responseData; } ScriptValue getResponseText() const { return _engine->newValue(QString(_rawResponseData.data())); } QString getResponseType() const { return _responseType; } void setResponseType(const QString& responseType) { _responseType = responseType; } ScriptValue getReadyState() const { return _engine->newValue(_readyState); } ScriptValue getError() const { return _engine->newValue(_errorCode); } ScriptValue getStatus() const; QString getStatusText() const; ScriptValue getOnTimeout() const { return _onTimeout; } void setOnTimeout(const ScriptValue& function) { _onTimeout = function; } ScriptValue getOnReadyStateChange() const { return _onReadyStateChange; } void setOnReadyStateChange(const ScriptValue& function) { _onReadyStateChange = function; } public slots: /*@jsdoc * Aborts the request. * @function XMLHttpRequest.abort */ void abort(); /*@jsdoc * Sets the value of an HTTP request header. Must be called after {@link XMLHttpRequest.open} but before * {@link XMLHttpRequest.send}; * @function XMLHttpRequest.setRequestHeader * @param {string} name - The name of the header to set. * @param {string} value - The value of the header. */ void setRequestHeader(const QString& name, const QString& value); /*@jsdoc * Initializes a request. * @function XMLHttpRequest.open * @param {string} method - The HTTP request method * to use, e.g., "GET", "POST", "PUT", or "DELETE". * @param {string} url - The URL to send the request to. * @param {boolean} [async=true] - true if the method returns without waiting for the response, * false if the method returns only once the request is complete. * @param {string} [username=""] - User name for authentication. * @param {string} [password=""] - Password for authentication. */ void open(const QString& method, const QString& url, bool async = true, const QString& username = "", const QString& password = ""); /*@jsdoc * Sends the request to the server. * @function XMLHttpRequest.send * @param {*} [data] - The data to send. */ void send(); void send(const ScriptValue& data); /*@jsdoc * Gets the response headers. * @function XMLHttpRequest.getAllResponseHeaders * @returns {string} The response headers, separated by "\n" characters. */ ScriptValue getAllResponseHeaders() const; /*@jsdoc * Gets a response header. * @function XMLHttpRequest.getResponseHeader * @param {string} name - * @returns {string} The response header. */ ScriptValue getResponseHeader(const QString& name) const; signals: /*@jsdoc * Triggered when the request is complete — with or without error (incl. timeout). * @function XMLHttpRequest.requestComplete * @returns {Signal} */ void requestComplete(); private: void setReadyState(ReadyState readyState); void doSend(); void connectToReply(QNetworkReply* reply); void disconnectFromReply(QNetworkReply* reply); void abortRequest(); ScriptEngine* _engine { nullptr }; bool _async { true }; QUrl _url; QString _method; QString _responseType; QNetworkRequest _request; QNetworkReply* _reply { nullptr }; QByteArray _sendData; QByteArray _rawResponseData; ScriptValue _responseData; ScriptValue _onTimeout; ScriptValue _onReadyStateChange; ReadyState _readyState { XMLHttpRequestClass::UNSENT }; /*@jsdoc *

The type of network error.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ValueNameDescription
0NoErrorNo error.
1ConnectionRefusedErrorThe server refused the connection.
2RemoteHostClosedErrorThe server closed the connection.
3HostNotFoundErrorHost name not found.
4TimeoutErrorConnection timed out
5OperationCanceledErrorOperation canceled by * {@link XMLHttpRequest.abort}.
6SslHandshakeFailedErrorSSL/TLS handshake failed.
7TemporaryNetworkFailureErrorTemporarily disconnected from the * network.
8NetworkSessionFailedErrorDisconnection from the network.
9BackgroundRequestNotAllowedErrorBackground request not allowed.
10TooManyRedirectsErrorToo many redirects.
11InsecureRedirectErrorRedirect from secure to insecure protocol.
101ProxyConnectionRefusedErrorConnection to proxy server refused.
102ProxyConnectionClosedErrorProxy server closed the connection.
103ProxyNotFoundErrorProxy host name not found.
104ProxyTimeoutErrorProxy connection timed out.
105ProxyAuthenticationRequiredErrorProxy requires authentication.
201ContentAccessDeniedAccess denied.
202ContentOperationNotPermittedErrorOperation not permitted.
203ContentNotFoundErrorContent not found.
204AuthenticationRequiredErrorAuthentication required.
205ContentReSendErrorResend failed.
206ContentConflictErrorResource state conflict.
207ContentGoneErrorResource no longer available.
401InternalServerErrorInternal server error.
402OperationNotImplementedErrorOperation not supported.
403ServiceUnavailableErrorRequest not able to be handled at this * time.
301ProtocolUnknownErrorProtocol unknown.
302ProtocolInvalidOperationErrorOperation invalid fro protocol.
99UnknownNetworkErrorUnknown network-related error.
199UnknownProxyErrorUnknown proxy-related error.
299UnknownContentErrorUnknown content-related error.
399ProtocolFailureProtocol error.
499UnknownServerErrorUnknown server response error.
* @typedef {number} XMLHttpRequest.NetworkError */ QNetworkReply::NetworkError _errorCode { QNetworkReply::NoError }; int _timeout { 0 }; QTimer _timer; int _numRedirects { 0 }; private slots: void requestFinished(); void requestError(QNetworkReply::NetworkError code); void requestMetaDataChanged(); void requestDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void requestTimeout(); }; #endif // hifi_XMLHttpRequestClass_h /// @}