@jaricardodev/ews-javascript-api
Version:
EWS Managed api in JavaScript
464 lines (418 loc) • 20 kB
text/typescript
import { ArrayHelper, StringHelper, xml2JsObject, DOMParser } from "../../ExtensionMethods";
import { EwsLogging } from "../EwsLogging";
import { EwsServiceXmlReader } from "../EwsServiceXmlReader";
import { Exception } from "../../Exceptions/Exception";
import { ExchangeService } from "../ExchangeService";
import { HangingRequestDisconnectEventArgs } from "./HangingRequestDisconnectEventArgs";
import { HangingRequestDisconnectReason } from "../../Enumerations/HangingRequestDisconnectReason";
import { IXHROptions, IXHRApi, IXHRProgress } from "../../Interfaces";
import { ServiceRequestBase } from "./ServiceRequestBase";
/**
* @internal Represents an abstract, hanging service request.
*/
export class HangingServiceRequestBase extends ServiceRequestBase {
private static BufferSize: number = 4096;
static LogAllWireBytes: boolean;
private responseHandler: HandleResponseObject = null;
private response: IXHROptions = null;
private request: IXHROptions = null;
heartbeatFrequencyMilliseconds: number = 0;
private lockObject: any;
/**
* @internal Occurs when the hanging request is disconnected. events converted into array of delagte function
*/
OnDisconnect: HangingRequestDisconnectHandler[] = [];
IsConnected: boolean = false;
/**
* ews-javascript-api: Response Headers delegate
*/
OnResponseHeader: Function;
// /**
// * ews-javascript-api: FetchStream object
// */
// private stream: FetchStream;
/**
* @internal Initializes a new instance of the **HangingServiceRequestBase** class.
*
* @param {ExchangeService} service The service.
* @param {HandleResponseObject} handler Callback delegate to handle response objects.
* @param {number} heartbeatFrequency Frequency at which we expect heartbeats, in milliseconds.
*/
constructor(service: ExchangeService, handler: HandleResponseObject, heartbeatFrequency: number) {
super(service);
this.responseHandler = handler;
this.heartbeatFrequencyMilliseconds = heartbeatFrequency;
}
/**
* Disconnects the request.
*/
Disconnect(): void
/**
* Disconnects the request with the specified reason and exception.
*
* @param {HangingRequestDisconnectReason} reason The reason.
* @param {Exception} exception The exception.
*/
Disconnect(reason: HangingRequestDisconnectReason, exception: Exception): void;
Disconnect(reason: HangingRequestDisconnectReason = HangingRequestDisconnectReason.UserInitiated, exception: Exception = null): void {
if (this.IsConnected) {
this.Service.XHRApi.disconnect();
this.InternalOnDisconnect(reason, exception);
}
}
/**
* Stores chunked data from fetch FetchStream
*/
private chunk: string = '';
/**
* @internal Exectures the request.
*/
InternalExecute(): Promise<void> {
//lock (this.lockObject){
//this.response = this.ValidateAndEmitRequest(this.BuildXHR());
return new Promise<void>((successDelegate, errorDelegate) => {
var request = this.BuildXHR();
//this.ReadResponsePrivate(response);
this.ValidateAndEmitRequest(request, (progress: IXHRProgress) => {
switch (progress.type) {
case "data":
this.InternalOnConnect();
progress.data = progress.data.trim();
this.chunk += progress.data;
let _continue = false;
let xml = '';
if (!StringHelper.IsNullOrEmpty(this.chunk)) {
//"<Envelope>indexOf</Envelope>"
let start = this.chunk.indexOf("<Envelope");
let end = this.chunk.indexOf("</Envelope>")
if (start >= 0 && end > 0) {
xml = this.chunk.substr(start, end - start + 11)
this.chunk = this.chunk.substr(end + 11);
_continue = true;
}
}
if (_continue) {
var dom = new DOMParser();
var xml2js = new xml2JsObject();
let req;
try {
//req = xml2js.parseXMLNode(dom.parseFromString(xml, "text/xml").documentElement, true);
//EwsLogging.DebugLog(req, true);
EwsLogging.DebugLog(xml, true);
var ewsXmlReader: EwsServiceXmlReader = new EwsServiceXmlReader(xml, this.Service);
EwsLogging.DebugLog(ewsXmlReader.JsObject, true);
//var serviceResponse =
this.ParseResponses(ewsXmlReader.JsObject);
// if (successDelegate)
// successDelegate(serviceResponse || xml);
// else {
// if (errorDelegate)
// errorDelegate(xml);
// }
} catch (error) {
if (errorDelegate)
errorDelegate(error);
}
}
break;
case "header":
this.InternalOnConnect();
if (this.OnResponseHeader && typeof this.OnResponseHeader === 'function') {
this.OnResponseHeader(progress.headers);
}
//console.log(meta);
break;
case "end":
this.InternalOnDisconnect(HangingRequestDisconnectReason.Clean, null);
break;
case "error":
this.Disconnect(HangingRequestDisconnectReason.Exception, <any>progress.error);
if (errorDelegate) {
errorDelegate(progress.error);
}
break;
default:
break;
}
}).then((xhrResponse: any) => { //<any> used for progress delegate, not in standard promise
//console.log(xhrResponse);
//successDelegate(void 0);
}, (resperr: XMLHttpRequest) => {
if (resperr.status && resperr.getAllResponseHeaders) {
EwsLogging.Log("Error in calling service, error code: " + resperr.status + "\r\n " + resperr.getAllResponseHeaders());
}
else {
EwsLogging.Log("Error in calling service, error code: " + (resperr.status || (<any>resperr).message));
}
if (errorDelegate) errorDelegate(this.ProcessWebException(resperr) || resperr);
});
});
}
/**
* Perform any bookkeeping needed when we connect
*/
private InternalOnConnect(): void {
if (!this.IsConnected) {
this.IsConnected = true;
// Trace Http headers
// this.Service.ProcessHttpResponseHeaders(
// TraceFlags.EwsResponseHttpHeaders,
// this.response);
//info: //ref: - not needed, no threadpool, handled in
// ThreadPool.QueueUserWorkItem(
// new WaitCallback(this.ParseResponses));
}
}
/**
* Perform any bookkeeping needed when we disconnect (cleanly or forcefully)
*
* @param {HangingRequestDisconnectReason} reason [description]
* @param {Exception} exception [description]
*/
InternalOnDisconnect(reason: HangingRequestDisconnectReason, exception: Exception): void {
if (this.IsConnected) {
this.IsConnected = false;
if (this.OnDisconnect && ArrayHelper.isArray(this.OnDisconnect)) {
try {
this.OnDisconnect.forEach((OnDisconnect) => {
OnDisconnect(this,
new HangingRequestDisconnectEventArgs(reason, exception));
});
}
catch (e) { }
}
}
}
//* @return {any} parsed response object.
/**
* Parses the responses.
*
* @param {any} state Notification state.
*/
private ParseResponses(state: any): void {
try {
let responseObject = this.ReadResponseXmlJsObject(state);
this.responseHandler(responseObject);
} catch (error) {
this.Disconnect(HangingRequestDisconnectReason.Exception, error);
console.log("error parsing object....\n TODO: Implement better parse error for Notifications");
//console.log(state);
}
// try
// {
// Guid traceId = Guid.Empty;
// HangingTraceStream tracingStream = null;
// MemoryStream responseCopy = null;
// try
// {
// bool traceEwsResponse = this.Service.IsTraceEnabledFor(TraceFlags.EwsResponse);
// using (Stream responseStream = this.response.GetResponseStream())
// {
// responseStream.ReadTimeout = 2 * this.heartbeatFrequencyMilliseconds;
// tracingStream = new HangingTraceStream(responseStream, this.Service);
// // EwsServiceMultiResponseXmlReader.Create causes a read.
// if (traceEwsResponse)
// {
// responseCopy = new MemoryStream();
// tracingStream.SetResponseCopy(responseCopy);
// }
// EwsServiceMultiResponseXmlReader ewsXmlReader = EwsServiceMultiResponseXmlReader.Create(tracingStream, this.Service);
// while (this.IsConnected)
// {
// object responseObject = null;
// if (traceEwsResponse)
// {
// try
// {
// responseObject = this.ReadResponse(ewsXmlReader, this.response.Headers);
// }
// finally
// {
// this.Service.TraceXml(TraceFlags.EwsResponse, responseCopy);
// }
// // reset the stream collector.
// responseCopy.Close();
// responseCopy = new MemoryStream();
// tracingStream.SetResponseCopy(responseCopy);
// }
// else
// {
// responseObject = this.ReadResponse(ewsXmlReader, this.response.Headers);
// }
// this.responseHandler(responseObject);
// }
// }
// }
// catch (TimeoutException ex)
// {
// // The connection timed out.
// this.Disconnect(HangingRequestDisconnectReason.Timeout, ex);
// return;
// }
// catch (IOException ex)
// {
// // Stream is closed, so disconnect.
// this.Disconnect(HangingRequestDisconnectReason.Exception, ex);
// return;
// }
// catch (HttpException ex)
// {
// // Stream is closed, so disconnect.
// this.Disconnect(HangingRequestDisconnectReason.Exception, ex);
// return;
// }
// catch (WebException ex)
// {
// // Stream is closed, so disconnect.
// this.Disconnect(HangingRequestDisconnectReason.Exception, ex);
// return;
// }
// catch (ObjectDisposedException ex)
// {
// // Stream is closed, so disconnect.
// this.Disconnect(HangingRequestDisconnectReason.Exception, ex);
// return;
// }
// catch (NotSupportedException)
// {
// // This is thrown if we close the stream during a read operation due to a user method call.
// // Trying to delay closing until the read finishes simply results in a long-running connection.
// this.Disconnect(HangingRequestDisconnectReason.UserInitiated, null);
// return;
// }
// catch (XmlException ex)
// {
// // Thrown if server returned no XML document.
// this.Disconnect(HangingRequestDisconnectReason.UserInitiated, ex);
// return;
// }
// finally
// {
// if (responseCopy != null)
// {
// responseCopy.Dispose();
// responseCopy = null;
// }
// }
// }
// catch (ServiceLocalException exception)
// {
// this.Disconnect(HangingRequestDisconnectReason.Exception, exception);
// }
}
//ReadPreamble(ewsXmlReader: EwsServiceXmlReader): void { throw new Error("HangingServiceRequestBase.ts - ReadPreamble : Not implemented."); }
/* ews-javascript-api specific */
/**
* Validates request parameters, and emits the request to the server.
*
* @param {IXHROptions} request The request.
* @return {Promise<XMLHttpRequest>} The response returned by the server.
*/
protected ValidateAndEmitRequest(request: IXHROptions, progressDelegate?: (progressData: IXHRProgress) => void): Promise<any> {
this.Validate();
//var request = this.BuildXHR();
if (this.Service.SendClientLatencies) {
var clientStatisticsToAdd: string = '';
//lock(clientStatisticsCache)
//{
if (ServiceRequestBase.clientStatisticsCache.length > 0) {
clientStatisticsToAdd = ServiceRequestBase.clientStatisticsCache[0];
ServiceRequestBase.clientStatisticsCache.splice(0, 1);
}
//}
if (!StringHelper.IsNullOrEmpty(clientStatisticsToAdd)) {
if (request.headers[ServiceRequestBase.ClientStatisticsRequestHeader]) {
request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] =
request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] + clientStatisticsToAdd;
}
else {
request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] = clientStatisticsToAdd;
}
}
}
//var startTime = Date.now();// DateTime.UtcNow;
//var response = XHR(request);
EwsLogging.DebugLog("sending ews request");
EwsLogging.DebugLog({ ...request, ...{ headers: { ...request.headers, Authorization: "REDACTED" } }}, true);
return this.Service.XHRApi.xhrStream(request, progressDelegate);
// return new Promise((successDelegate, errorDelegate) => {
// this.stream = new FetchStream(this.Service.Url.ToString(), request);
// this.stream.on("data", (chunk) => {
// //console.log(chunk.toString());
// progressDelegate(chunk.toString());
// });
// this.stream.on("meta", (meta) => {
// if (this.OnResponseHeader && typeof this.OnResponseHeader === 'function') {
// this.OnResponseHeader(meta["responseHeaders"])
// }
// //console.log(meta);
// });
// this.stream.on("end", (data) => {
// this.IsConnected = false;
// });
// this.stream.on('error', (error) => {
// this.Disconnect(HangingRequestDisconnectReason.Exception, <any>error);
// if (errorDelegate) {
// errorDelegate(error);
// }
// });
// });
//try
//{
// var response = this.GetEwsHttpWebResponse(request);
//}
//finally {
// if (this.service.SendClientLatencies) {
// int clientSideLatency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
// string requestId = string.Empty;
// string soapAction = this.GetType().Name.Replace("Request", string.Empty);
// if (response != null && response.Headers != null) {
// foreach(string requestIdHeader in ServiceRequestBase.RequestIdResponseHeaders)
// {
// string requestIdValue = response.Headers.Get(requestIdHeader);
// if (!string.IsNullOrEmpty(requestIdValue)) {
// requestId = requestIdValue;
// break;
// }
// }
// }
// StringBuilder sb = new StringBuilder();
// sb.Append("MessageId=");
// sb.Append(requestId);
// sb.Append(",ResponseTime=");
// sb.Append(clientSideLatency);
// sb.Append(",SoapAction=");
// sb.Append(soapAction);
// sb.Append(";");
// lock(clientStatisticsCache)
// {
// clientStatisticsCache.Add(sb.ToString());
// }
// }
//}
//return response;
}
}
/**
* @internal interface to declare Delegate method to handle a hanging request disconnection.
*/
export interface HangingRequestDisconnectHandler {
/**
* Delegate method to handle a hanging request disconnection.
*
* @param {any} sender The object invoking the delegate.
* @param {HangingRequestDisconnectEventArgs} args Event data.
*/
(sender: any, args: HangingRequestDisconnectEventArgs): void;
}
/**
* @internal interface to declare Callback delegate to handle asynchronous responses.
*/
export interface HandleResponseObject {
/**
* Callback delegate to handle asynchronous responses.
*
* @param {any} response Response received from the server
*/
(response: any): void;
}