{"version":3,"file":"batch.mjs","names":[],"sources":["../../src/transport/batch.ts"],"sourcesContent":["import { DOCS_TROUBLESHOOTING, MORE_DETAILS, display } from '../tools/display'\nimport type { Context } from '../tools/serialisation/context'\nimport { objectValues } from '../tools/utils/polyfills'\nimport { isPageExitReason } from '../browser/pageMayExitObservable'\nimport { jsonStringify } from '../tools/serialisation/jsonStringify'\nimport type { Encoder, EncoderResult } from '../tools/encoder'\nimport { computeBytesCount, ONE_KIBI_BYTE } from '../tools/utils/byteUtils'\nimport type { HttpRequest, Payload } from './httpRequest'\nimport type { FlushController, FlushEvent } from './flushController'\n\nexport const MESSAGE_BYTES_LIMIT = 256 * ONE_KIBI_BYTE\n\nexport interface Batch {\n  flushController: FlushController\n  add: (message: Context) => void\n  upsert: (message: Context, key: string) => void\n  stop: () => void\n}\n\nexport function createBatch({\n  encoder,\n  request,\n  flushController,\n}: {\n  encoder: Encoder\n  request: HttpRequest\n  flushController: FlushController\n}): Batch {\n  let upsertBuffer: { [key: string]: string } = {}\n  const flushSubscription = flushController.flushObservable.subscribe((event) => flush(event))\n\n  function push(serializedMessage: string, estimatedMessageBytesCount: number, key?: string) {\n    if (key !== undefined) {\n      let bytesDiff: number\n      if (upsertBuffer[key] !== undefined) {\n        bytesDiff = estimatedMessageBytesCount - encoder.estimateEncodedBytesCount(upsertBuffer[key])\n      } else {\n        flushController.notifyBeforeAddMessage(estimatedMessageBytesCount)\n        bytesDiff = 0\n      }\n      upsertBuffer[key] = serializedMessage\n      flushController.notifyAfterAddMessage(bytesDiff)\n    } else {\n      flushController.notifyBeforeAddMessage(estimatedMessageBytesCount)\n      encoder.write(encoder.isEmpty ? serializedMessage : `\\n${serializedMessage}`, (realMessageBytesCount) => {\n        flushController.notifyAfterAddMessage(realMessageBytesCount - estimatedMessageBytesCount)\n      })\n    }\n  }\n\n  function addOrUpdate(message: Context, key?: string) {\n    const serializedMessage = jsonStringify(message)!\n\n    const estimatedMessageBytesCount = encoder.estimateEncodedBytesCount(serializedMessage)\n\n    if (estimatedMessageBytesCount >= MESSAGE_BYTES_LIMIT) {\n      display.warn(\n        `Discarded a message whose size was bigger than the maximum allowed size ${MESSAGE_BYTES_LIMIT / ONE_KIBI_BYTE}KiB. ${MORE_DETAILS} ${DOCS_TROUBLESHOOTING}/#technical-limitations`\n      )\n      return\n    }\n\n    push(serializedMessage, estimatedMessageBytesCount, key)\n  }\n\n  function flush(event: FlushEvent) {\n    const upsertMessages = objectValues(upsertBuffer).join('\\n')\n    upsertBuffer = {}\n\n    const pageMightExit = isPageExitReason(event.reason)\n    const send = pageMightExit ? request.sendOnExit : request.send\n\n    if (\n      pageMightExit &&\n      // Note: checking that the encoder is async is not strictly needed, but it's an optimization:\n      // if the encoder is async we need to send two requests in some cases (one for encoded data\n      // and the other for non-encoded data). But if it's not async, we don't have to worry about\n      // it and always send a single request.\n      encoder.isAsync\n    ) {\n      const encoderResult = encoder.finishSync()\n\n      // Send encoded messages\n      if (encoderResult.outputBytesCount) {\n        send(formatPayloadFromEncoder(encoderResult))\n      }\n\n      // Send messages that are not yet encoded at this point\n      const pendingMessages = [encoderResult.pendingData, upsertMessages].filter(Boolean).join('\\n')\n      if (pendingMessages) {\n        send({\n          data: pendingMessages,\n          bytesCount: computeBytesCount(pendingMessages),\n        })\n      }\n    } else {\n      if (upsertMessages) {\n        encoder.write(encoder.isEmpty ? upsertMessages : `\\n${upsertMessages}`)\n      }\n      encoder.finish((encoderResult) => {\n        send(formatPayloadFromEncoder(encoderResult))\n      })\n    }\n  }\n\n  return {\n    flushController,\n    add: addOrUpdate,\n    upsert: addOrUpdate,\n    stop: flushSubscription.unsubscribe,\n  }\n}\n\nfunction formatPayloadFromEncoder(encoderResult: EncoderResult): Payload {\n  let data: string | Blob\n  if (typeof encoderResult.output === 'string') {\n    data = encoderResult.output\n  } else {\n    data = new Blob([encoderResult.output], {\n      // This will set the 'Content-Type: text/plain' header. Reasoning:\n      // * The intake rejects the request if there is no content type.\n      // * The browser will issue CORS preflight requests if we set it to 'application/json', which\n      // could induce higher intake load (and maybe has other impacts).\n      // * Also it's not quite JSON, since we are concatenating multiple JSON objects separated by\n      // new lines.\n      type: 'text/plain',\n    })\n  }\n\n  return {\n    data,\n    bytesCount: encoderResult.outputBytesCount,\n    encoding: encoderResult.encoding,\n  }\n}\n"],"mappings":";;;;;;AAUA,MAAa,sBAAsB,MAAM;AASzC,SAAgB,YAAY,EAC1B,SACA,SACA,mBAKQ;CACR,IAAI,eAA0C,CAAC;CAC/C,MAAM,oBAAoB,gBAAgB,gBAAgB,WAAW,UAAU,MAAM,KAAK,CAAC;CAE3F,SAAS,KAAK,mBAA2B,4BAAoC,KAAc;EACzF,IAAI,QAAQ,KAAA,GAAW;GACrB,IAAI;GACJ,IAAI,aAAa,SAAS,KAAA,GACxB,YAAY,6BAA6B,QAAQ,0BAA0B,aAAa,IAAI;QACvF;IACL,gBAAgB,uBAAuB,0BAA0B;IACjE,YAAY;GACd;GACA,aAAa,OAAO;GACpB,gBAAgB,sBAAsB,SAAS;EACjD,OAAO;GACL,gBAAgB,uBAAuB,0BAA0B;GACjE,QAAQ,MAAM,QAAQ,UAAU,oBAAoB,KAAK,sBAAsB,0BAA0B;IACvG,gBAAgB,sBAAsB,wBAAwB,0BAA0B;GAC1F,CAAC;EACH;CACF;CAEA,SAAS,YAAY,SAAkB,KAAc;EACnD,MAAM,oBAAoB,cAAc,OAAO;EAE/C,MAAM,6BAA6B,QAAQ,0BAA0B,iBAAiB;EAEtF,IAAI,8BAA8B,qBAAqB;GACrD,QAAQ,KACN,2EAA2E,sBAAsB,cAAc,OAAO,aAAa,GAAG,qBAAqB,wBAC7J;GACA;EACF;EAEA,KAAK,mBAAmB,4BAA4B,GAAG;CACzD;CAEA,SAAS,MAAM,OAAmB;EAChC,MAAM,iBAAiB,aAAa,YAAY,CAAC,CAAC,KAAK,IAAI;EAC3D,eAAe,CAAC;EAEhB,MAAM,gBAAgB,iBAAiB,MAAM,MAAM;EACnD,MAAM,OAAO,gBAAgB,QAAQ,aAAa,QAAQ;EAE1D,IACE,iBAKA,QAAQ,SACR;GACA,MAAM,gBAAgB,QAAQ,WAAW;GAGzC,IAAI,cAAc,kBAChB,KAAK,yBAAyB,aAAa,CAAC;GAI9C,MAAM,kBAAkB,CAAC,cAAc,aAAa,cAAc,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI;GAC7F,IAAI,iBACF,KAAK;IACH,MAAM;IACN,YAAY,kBAAkB,eAAe;GAC/C,CAAC;EAEL,OAAO;GACL,IAAI,gBACF,QAAQ,MAAM,QAAQ,UAAU,iBAAiB,KAAK,gBAAgB;GAExE,QAAQ,QAAQ,kBAAkB;IAChC,KAAK,yBAAyB,aAAa,CAAC;GAC9C,CAAC;EACH;CACF;CAEA,OAAO;EACL;EACA,KAAK;EACL,QAAQ;EACR,MAAM,kBAAkB;CAC1B;AACF;AAEA,SAAS,yBAAyB,eAAuC;CACvE,IAAI;CACJ,IAAI,OAAO,cAAc,WAAW,UAClC,OAAO,cAAc;MAErB,OAAO,IAAI,KAAK,CAAC,cAAc,MAAM,GAAG,EAOtC,MAAM,aACR,CAAC;CAGH,OAAO;EACL;EACA,YAAY,cAAc;EAC1B,UAAU,cAAc;CAC1B;AACF"}