UNPKG

@jaricardodev/ews-javascript-api

Version:
464 lines (418 loc) 20 kB
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; }