{"version":3,"file":"xhrObservable.mjs","names":[],"sources":["../../src/browser/xhrObservable.ts"],"sourcesContent":["import { elapsed, timeStampNow, clocksNow } from '@openobserve/js-core/time'\nimport type { Duration, ClocksState } from '@openobserve/js-core/time'\nimport type { InstrumentedMethodCall } from '../tools/instrumentMethod'\nimport { instrumentMethod } from '../tools/instrumentMethod'\nimport { Observable } from '../tools/observable'\nimport { normalizeUrl } from '../tools/utils/urlPolyfill'\nimport { globalObject } from '../tools/globalObject'\nimport { addEventListener } from './addEventListener'\n\nexport interface XhrOpenContext {\n  state: 'open'\n  method: string\n  url: string\n}\n\nexport interface XhrStartContext extends Omit<XhrOpenContext, 'state'> {\n  state: 'start'\n  startClocks: ClocksState\n  isAborted: boolean\n  xhr: XMLHttpRequest\n  handlingStack?: string\n  requestBody?: unknown\n}\n\nexport interface XhrCompleteContext extends Omit<XhrStartContext, 'state'> {\n  state: 'complete'\n  duration: Duration\n  status: number\n  responseBody?: string\n}\n\nexport type XhrContext = XhrOpenContext | XhrStartContext | XhrCompleteContext\n\nlet xhrObservable: Observable<XhrContext> | undefined\nconst xhrContexts = new WeakMap<XMLHttpRequest, XhrContext>()\n\nexport function initXhrObservable() {\n  if (!xhrObservable) {\n    xhrObservable = createXhrObservable()\n  }\n  return xhrObservable\n}\n\nfunction createXhrObservable() {\n  if (!('XMLHttpRequest' in globalObject)) {\n    return new Observable<XhrContext>()\n  }\n\n  return new Observable<XhrContext>((observable) => {\n    const { stop: stopInstrumentingStart } = instrumentMethod(XMLHttpRequest.prototype, 'open', openXhr)\n\n    const { stop: stopInstrumentingSend } = instrumentMethod(\n      XMLHttpRequest.prototype,\n      'send',\n      (call) => {\n        sendXhr(call, observable)\n      },\n      { computeHandlingStack: true }\n    )\n\n    const { stop: stopInstrumentingAbort } = instrumentMethod(XMLHttpRequest.prototype, 'abort', abortXhr)\n\n    return () => {\n      stopInstrumentingStart()\n      stopInstrumentingSend()\n      stopInstrumentingAbort()\n    }\n  })\n}\n\nfunction openXhr({ target: xhr, parameters: [method, url] }: InstrumentedMethodCall<XMLHttpRequest, 'open'>) {\n  xhrContexts.set(xhr, {\n    state: 'open',\n    method: String(method).toUpperCase(),\n    url: normalizeUrl(String(url)),\n  })\n}\n\nfunction sendXhr(\n  { target: xhr, parameters: [body], handlingStack }: InstrumentedMethodCall<XMLHttpRequest, 'send'>,\n  observable: Observable<XhrContext>\n) {\n  const context = xhrContexts.get(xhr)\n  if (!context) {\n    return\n  }\n\n  const startContext = context as XhrStartContext\n  startContext.state = 'start'\n  startContext.startClocks = clocksNow()\n  startContext.isAborted = false\n  startContext.xhr = xhr\n  startContext.handlingStack = handlingStack\n  startContext.requestBody = body\n\n  let hasBeenReported = false\n\n  const { stop: stopInstrumentingOnReadyStateChange } = instrumentMethod(xhr, 'onreadystatechange', () => {\n    if (xhr.readyState === XMLHttpRequest.DONE) {\n      // Try to report the XHR as soon as possible, because the XHR may be mutated by the\n      // application during a future event. For example, Angular is calling .abort() on\n      // completed requests during an onreadystatechange event, so the status becomes '0'\n      // before the request is collected.\n      onEnd()\n    }\n  })\n\n  const onEnd = () => {\n    unsubscribeLoadEndListener()\n    stopInstrumentingOnReadyStateChange()\n    if (hasBeenReported) {\n      return\n    }\n    hasBeenReported = true\n\n    const completeContext = context as XhrCompleteContext\n    completeContext.duration = elapsed(startContext.startClocks.timeStamp, timeStampNow())\n    completeContext.status = xhr.status\n    if (typeof xhr.response === 'string') {\n      completeContext.responseBody = xhr.response\n    }\n    observable.notify({ ...completeContext, state: 'complete' })\n  }\n\n  const { stop: unsubscribeLoadEndListener } = addEventListener(xhr, 'loadend', onEnd)\n\n  observable.notify(startContext)\n}\n\nfunction abortXhr({ target: xhr }: InstrumentedMethodCall<XMLHttpRequest, 'abort'>) {\n  const context = xhrContexts.get(xhr) as XhrStartContext | undefined\n  if (context) {\n    context.isAborted = true\n  }\n}\n\n/**\n * Reset the XHR observable global state. This is useful for testing to ensure clean state between tests.\n *\n * @internal\n */\nexport function resetXhrObservable() {\n  xhrObservable = undefined\n}\n"],"mappings":";;;;;;;AAiCA,IAAI;AACJ,MAAM,8BAAc,IAAI,QAAoC;AAE5D,SAAgB,oBAAoB;CAClC,IAAI,CAAC,eACH,gBAAgB,oBAAoB;CAEtC,OAAO;AACT;AAEA,SAAS,sBAAsB;CAC7B,IAAI,EAAE,oBAAoB,eACxB,OAAO,IAAI,WAAuB;CAGpC,OAAO,IAAI,YAAwB,eAAe;EAChD,MAAM,EAAE,MAAM,2BAA2B,iBAAiB,eAAe,WAAW,QAAQ,OAAO;EAEnG,MAAM,EAAE,MAAM,0BAA0B,iBACtC,eAAe,WACf,SACC,SAAS;GACR,QAAQ,MAAM,UAAU;EAC1B,GACA,EAAE,sBAAsB,KAAK,CAC/B;EAEA,MAAM,EAAE,MAAM,2BAA2B,iBAAiB,eAAe,WAAW,SAAS,QAAQ;EAErG,aAAa;GACX,uBAAuB;GACvB,sBAAsB;GACtB,uBAAuB;EACzB;CACF,CAAC;AACH;AAEA,SAAS,QAAQ,EAAE,QAAQ,KAAK,YAAY,CAAC,QAAQ,QAAwD;CAC3G,YAAY,IAAI,KAAK;EACnB,OAAO;EACP,QAAQ,OAAO,MAAM,CAAC,CAAC,YAAY;EACnC,KAAK,aAAa,OAAO,GAAG,CAAC;CAC/B,CAAC;AACH;AAEA,SAAS,QACP,EAAE,QAAQ,KAAK,YAAY,CAAC,OAAO,iBACnC,YACA;CACA,MAAM,UAAU,YAAY,IAAI,GAAG;CACnC,IAAI,CAAC,SACH;CAGF,MAAM,eAAe;CACrB,aAAa,QAAQ;CACrB,aAAa,cAAc,UAAU;CACrC,aAAa,YAAY;CACzB,aAAa,MAAM;CACnB,aAAa,gBAAgB;CAC7B,aAAa,cAAc;CAE3B,IAAI,kBAAkB;CAEtB,MAAM,EAAE,MAAM,wCAAwC,iBAAiB,KAAK,4BAA4B;EACtG,IAAI,IAAI,eAAe,eAAe,MAKpC,MAAM;CAEV,CAAC;CAED,MAAM,cAAc;EAClB,2BAA2B;EAC3B,oCAAoC;EACpC,IAAI,iBACF;EAEF,kBAAkB;EAElB,MAAM,kBAAkB;EACxB,gBAAgB,WAAW,QAAQ,aAAa,YAAY,WAAW,aAAa,CAAC;EACrF,gBAAgB,SAAS,IAAI;EAC7B,IAAI,OAAO,IAAI,aAAa,UAC1B,gBAAgB,eAAe,IAAI;EAErC,WAAW,OAAO;GAAE,GAAG;GAAiB,OAAO;EAAW,CAAC;CAC7D;CAEA,MAAM,EAAE,MAAM,+BAA+B,iBAAiB,KAAK,WAAW,KAAK;CAEnF,WAAW,OAAO,YAAY;AAChC;AAEA,SAAS,SAAS,EAAE,QAAQ,OAAwD;CAClF,MAAM,UAAU,YAAY,IAAI,GAAG;CACnC,IAAI,SACF,QAAQ,YAAY;AAExB"}