{"version":3,"file":"mjpeg-hub.mjs","sources":["../../src/mjpeg-hub.ts"],"sourcesContent":["import type { Agent as PageAgent } from '@midscene/core/agent';\nimport type {\n  MjpegStreamFrame,\n  MjpegStreamHandle,\n} from '@midscene/core/device';\nimport { type DebugFunction, getDebug } from '@midscene/shared/logger';\nimport type { Request, Response } from 'express';\n\nconst DATA_URL_BASE64_PREFIX = /^data:image\\/\\w+;base64,/;\n\nconst noopDebug: DebugFunction = () => {};\n\ntype ActiveInterface = PageAgent['interface'];\n\ntype Subscriber = (frame: MjpegStreamFrame) => void;\n\ninterface InternalProducer {\n  source: ActiveInterface;\n  controller: AbortController;\n  handle?: MjpegStreamHandle;\n  lastFrame?: MjpegStreamFrame;\n  startupError?: unknown;\n  firstFrameReady: Promise<boolean>;\n  subscribers: Set<Subscriber>;\n  /** Tracks `res` instances per subscriber so the hub can hard-close them. */\n  responses: Map<Subscriber, Response>;\n  stopTimer?: ReturnType<typeof setTimeout>;\n}\n\nexport interface InterfaceMjpegHubOptions {\n  /** Time the hub waits for the first producer frame before falling back. */\n  initialFrameTimeoutMs: number;\n  /** Idle window after the last subscriber leaves before tearing the producer down. */\n  idleStopMs: number;\n  /** Optional debug logger for hub internals. Defaults to a no-op. */\n  debug?: DebugFunction;\n}\n\n/**\n * Recovery hook supplied by the server. When the producer fails to start\n * because the underlying page session was closed, the hub asks the server to\n * rebuild the agent and returns the new interface; otherwise the hub gives up\n * and lets `streamRequest` resolve to false.\n */\nexport type RecoverActiveAgent = (\n  error: unknown,\n) => Promise<ActiveInterface | null>;\n\n/**\n * Writes one MJPEG part to `res`, preferring backpressure-safe writes.\n *\n * Returns `true` when the chunk has been accepted by the socket buffer and\n * `false` when the kernel buffer is full. Callers SHOULD drop frames or wait\n * for `drain` instead of pushing more data when this returns `false`.\n *\n * `frame.data` may either be raw base64 or a `data:image/...;base64,...` URL;\n * the function strips the prefix defensively. New producers should already\n * normalize to bare base64.\n */\nexport function writeMjpegFrame(\n  res: Response,\n  boundary: string,\n  frame: MjpegStreamFrame,\n): boolean {\n  const raw = frame.data.replace(DATA_URL_BASE64_PREFIX, '');\n  const buf = Buffer.from(raw, 'base64');\n\n  // Each `res.write` returns false when the kernel buffer is full. We\n  // surface the worst result so the caller can react to backpressure on the\n  // first chunk that exceeds the high water mark.\n  let writable = res.write(`--${boundary}\\r\\n`);\n  writable =\n    res.write(`Content-Type: ${frame.contentType || 'image/jpeg'}\\r\\n`) &&\n    writable;\n  writable = res.write(`Content-Length: ${buf.length}\\r\\n\\r\\n`) && writable;\n  writable = res.write(buf) && writable;\n  writable = res.write('\\r\\n') && writable;\n  return writable;\n}\n\nfunction endMjpegResponse(res: Response): void {\n  try {\n    res.end();\n  } catch {\n    /* response already closed */\n  }\n}\n\n/**\n * Owns the lifecycle of an in-process MJPEG frame producer (e.g. Chromium\n * CDP `Page.startScreencast`) and fans frames out to all currently connected\n * HTTP MJPEG clients.\n *\n * Why this is its own class:\n * - CDP screencasts are page-scoped, so multiple concurrent producers would\n *   steal frames from each other. Keeping a single producer + N subscribers\n *   here prevents the playground server from accidentally racing against\n *   itself.\n * - Producer creation, idle teardown, recovery after page-session loss and\n *   backpressure handling are all naturally co-located with the producer\n *   state. Moving them out of `PlaygroundServer` keeps that class focused on\n *   HTTP routing.\n */\nexport class InterfaceMjpegHub {\n  private producer?: InternalProducer;\n  private readonly debug: DebugFunction;\n\n  constructor(private readonly opts: InterfaceMjpegHubOptions) {\n    this.debug = opts.debug ?? noopDebug;\n  }\n\n  /**\n   * Streams the active interface's MJPEG frames to `res`. Returns true once\n   * the response is committed to streaming, false if the interface has no\n   * frame producer or the initial frame never arrived.\n   */\n  async streamRequest(\n    req: Request,\n    res: Response,\n    activeInterface: ActiveInterface,\n    recoverActiveAgent: RecoverActiveAgent,\n  ): Promise<boolean> {\n    return this.streamRequestInternal(\n      req,\n      res,\n      activeInterface,\n      recoverActiveAgent,\n      true,\n    );\n  }\n\n  /**\n   * Tears down the current producer (used when the server replaces an agent\n   * out-of-band, e.g. after a recoverable page-session error during /interact).\n   */\n  stopProducer(): void {\n    this.stopProducerInternal(this.producer);\n  }\n\n  /**\n   * Best-effort shutdown for server.close(). Aborts any active producer and\n   * forcibly closes attached subscriber sockets.\n   */\n  shutdown(): void {\n    const producer = this.producer;\n    if (!producer) return;\n    for (const [subscriber, res] of producer.responses) {\n      producer.subscribers.delete(subscriber);\n      try {\n        res.destroy();\n      } catch {\n        /* socket already closed */\n      }\n    }\n    producer.responses.clear();\n    this.stopProducerInternal(producer);\n  }\n\n  getLastFrame(): MjpegStreamFrame | undefined {\n    return this.producer?.lastFrame;\n  }\n\n  private async streamRequestInternal(\n    req: Request,\n    res: Response,\n    activeInterface: ActiveInterface,\n    recoverActiveAgent: RecoverActiveAgent,\n    allowRecovery: boolean,\n  ): Promise<boolean> {\n    const producer = this.getOrCreateProducer(activeInterface);\n    if (!producer) return false;\n\n    const hasInitialFrame = await producer.firstFrameReady;\n    if (!hasInitialFrame || !producer.lastFrame) {\n      this.debug(\n        'interface frame producer did not emit an initial frame, falling back to polling',\n      );\n      const startupError = producer.startupError;\n      this.stopProducerInternal(producer);\n      if (allowRecovery && startupError) {\n        const recoveredInterface = await recoverActiveAgent(startupError);\n        if (recoveredInterface) {\n          return this.streamRequestInternal(\n            req,\n            res,\n            recoveredInterface,\n            recoverActiveAgent,\n            false,\n          );\n        }\n      }\n      return false;\n    }\n\n    this.attachSubscriber(req, res, producer);\n    return true;\n  }\n\n  private attachSubscriber(\n    req: Request,\n    res: Response,\n    producer: InternalProducer,\n  ): void {\n    const boundary = 'mjpeg-boundary';\n    let closed = false;\n    let dropping = false;\n\n    const closeResponse = () => {\n      if (closed) return;\n      closed = true;\n      this.releaseSubscriber(producer, subscriber);\n    };\n\n    const subscriber: Subscriber = (frame) => {\n      if (closed) return;\n      // Drop frames while the socket buffer is full instead of letting the\n      // node internal buffer balloon. CDP screencasts can run at 60Hz and a\n      // slow client would otherwise OOM the server.\n      if (dropping) return;\n      try {\n        const writable = writeMjpegFrame(res, boundary, frame);\n        if (!writable) {\n          dropping = true;\n          res.once('drain', () => {\n            dropping = false;\n          });\n        }\n      } catch (error) {\n        this.debug('interface frame write failed: %s', error);\n        closeResponse();\n        try {\n          res.destroy();\n        } catch {\n          /* socket already closed */\n        }\n      }\n    };\n\n    // Chromium's <img> with multipart/x-mixed-replace keeps the underlying\n    // TCP connection alive even after the element is unmounted from the DOM —\n    // there's no FIN until something else cleans up. Each subsequent mount\n    // (Overview ↔ Device re-entry, React StrictMode double-mount, retry\n    // timer) opens a new socket without releasing the old one. Studio only\n    // ever has a single visible preview, so before attaching the new\n    // subscriber we end any stale ones — this both releases server-side\n    // resources and unblocks Chromium's per-origin connection slot quota\n    // (6 for HTTP/1.1). Without this, after a handful of re-mounts the\n    // browser cannot open any further /mjpeg request and shows a permanent\n    // blank canvas.\n    //\n    // Use graceful end for MJPEG responses so Chromium does not poison the\n    // connection pool with ERR_INCOMPLETE_CHUNKED_ENCODING and leave the next\n    // <img>.naturalWidth at 0.\n    for (const [oldSubscriber, oldRes] of producer.responses) {\n      producer.subscribers.delete(oldSubscriber);\n      producer.responses.delete(oldSubscriber);\n      endMjpegResponse(oldRes);\n    }\n\n    producer.subscribers.add(subscriber);\n    producer.responses.set(subscriber, res);\n    if (producer.stopTimer) {\n      clearTimeout(producer.stopTimer);\n      producer.stopTimer = undefined;\n    }\n    req.on('close', closeResponse);\n\n    res.setHeader(\n      'Content-Type',\n      `multipart/x-mixed-replace; boundary=${boundary}`,\n    );\n    res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');\n    res.setHeader('Connection', 'keep-alive');\n    this.flushInitialFrame(subscriber, producer.lastFrame as MjpegStreamFrame);\n\n    this.debug('streaming via shared interface frame producer');\n  }\n\n  /**\n   * Push the producer's cached frame to a freshly-attached subscriber.\n   *\n   * Why two writes:\n   *\n   * Chromium's `<img>` with `multipart/x-mixed-replace` only commits a part\n   * to display once it sees the *next* part's boundary delimiter — the\n   * boundary is what tells the decoder that the previous part's body is\n   * complete. When the CDP screencast is idle (page is past\n   * `waitForNetworkIdle`, no animation) the producer only ever pushes one\n   * frame: the cached `lastFrame`. With a single write, the browser holds\n   * onto the bytes but never paints them (`<img>.naturalWidth === 0`) — a\n   * permanent blank canvas while waiting for a frame that never arrives.\n   *\n   * The duplicate write below is a sentinel: the second part's leading\n   * boundary is exactly what unblocks the first part's commit. The second\n   * part itself never gets displayed (multipart only ever shows the latest\n   * committed part, and any subsequent real CDP frame overrides it), so\n   * the cost is one extra frame on the wire per subscriber attach. Without\n   * this, Overview → Device re-entry consistently leaves the user staring\n   * at white.\n   */\n  private flushInitialFrame(\n    subscriber: Subscriber,\n    lastFrame: MjpegStreamFrame,\n  ): void {\n    subscriber(lastFrame);\n    subscriber(lastFrame);\n  }\n\n  private getOrCreateProducer(\n    activeInterface: ActiveInterface,\n  ): InternalProducer | null {\n    const startMjpegStream = activeInterface.startMjpegStream;\n    if (typeof startMjpegStream !== 'function') return null;\n\n    if (this.producer?.source === activeInterface) {\n      if (this.producer.stopTimer) {\n        clearTimeout(this.producer.stopTimer);\n        this.producer.stopTimer = undefined;\n      }\n      return this.producer;\n    }\n\n    this.stopProducerInternal(this.producer);\n\n    const controller = new AbortController();\n    let resolveInitialFrame: ((hasFrame: boolean) => void) | undefined;\n    let initialFrameTimer: ReturnType<typeof setTimeout> | undefined;\n\n    const resolveInitialFrameOnce = (hasFrame: boolean) => {\n      if (!resolveInitialFrame) return;\n      if (initialFrameTimer) {\n        clearTimeout(initialFrameTimer);\n        initialFrameTimer = undefined;\n      }\n      resolveInitialFrame(hasFrame);\n      resolveInitialFrame = undefined;\n    };\n\n    const initialFrameReady = new Promise<boolean>((resolve) => {\n      resolveInitialFrame = resolve;\n      initialFrameTimer = setTimeout(() => {\n        resolveInitialFrameOnce(false);\n      }, this.opts.initialFrameTimeoutMs);\n    });\n\n    const producer: InternalProducer = {\n      source: activeInterface,\n      controller,\n      firstFrameReady: initialFrameReady,\n      subscribers: new Set(),\n      responses: new Map(),\n    };\n    this.producer = producer;\n\n    void (async () => {\n      try {\n        producer.handle =\n          (await startMjpegStream.call(activeInterface, {\n            signal: controller.signal,\n            onFrame: (frame) => {\n              if (controller.signal.aborted) return;\n              producer.lastFrame = frame;\n              resolveInitialFrameOnce(true);\n              for (const subscriber of producer.subscribers) {\n                subscriber(frame);\n              }\n            },\n            onError: (error) => {\n              this.debug('interface stream producer error: %s', error);\n              // Tear down the dead producer so the next /mjpeg request\n              // (triggered by the <img> onError → retry) constructs a\n              // fresh one. Without this, the dead producer is reused\n              // forever — explaining why even page.reload() can't\n              // recover the preview after an in-flight CDP screencast\n              // dies during a task run.\n              this.stopProducerInternal(producer);\n            },\n          })) ?? undefined;\n      } catch (error) {\n        this.debug('interface frame producer unavailable: %s', error);\n        producer.startupError = error;\n        resolveInitialFrameOnce(false);\n        this.stopProducerInternal(producer);\n      }\n    })();\n\n    return producer;\n  }\n\n  private stopProducerInternal(producer?: InternalProducer): void {\n    if (!producer) return;\n    if (producer.stopTimer) {\n      clearTimeout(producer.stopTimer);\n      producer.stopTimer = undefined;\n    }\n    // End any subscriber responses we still own. A hard destroy here can leave\n    // Chromium's multipart image loader stuck on a blank cached connection\n    // after the producer dies during navigation.\n    for (const [, res] of producer.responses) {\n      endMjpegResponse(res);\n    }\n    producer.responses.clear();\n    producer.subscribers.clear();\n    producer.controller.abort();\n    Promise.resolve(producer.handle?.stop?.()).catch((error) => {\n      this.debug('interface stream stop failed: %s', error);\n    });\n    if (this.producer === producer) {\n      this.producer = undefined;\n    }\n  }\n\n  private releaseSubscriber(\n    producer: InternalProducer,\n    subscriber: Subscriber,\n  ): void {\n    producer.subscribers.delete(subscriber);\n    producer.responses.delete(subscriber);\n    if (producer.subscribers.size > 0 || producer.stopTimer) return;\n    producer.stopTimer = setTimeout(() => {\n      producer.stopTimer = undefined;\n      if (producer.subscribers.size === 0) {\n        this.stopProducerInternal(producer);\n      }\n    }, this.opts.idleStopMs);\n  }\n}\n\n/**\n * Convenience constructor that wires up a debug logger derived from the\n * `web:mjpeg` namespace so server logs are consistent with other modules.\n */\nexport function createInterfaceMjpegHub(\n  opts: Omit<InterfaceMjpegHubOptions, 'debug'> & { debug?: DebugFunction },\n): InterfaceMjpegHub {\n  return new InterfaceMjpegHub({\n    ...opts,\n    debug: opts.debug ?? getDebug('playground:mjpeg-hub'),\n  });\n}\n"],"names":["DATA_URL_BASE64_PREFIX","noopDebug","writeMjpegFrame","res","boundary","frame","raw","buf","Buffer","writable","endMjpegResponse","InterfaceMjpegHub","req","activeInterface","recoverActiveAgent","producer","subscriber","allowRecovery","hasInitialFrame","startupError","recoveredInterface","closed","dropping","closeResponse","error","oldSubscriber","oldRes","clearTimeout","undefined","lastFrame","startMjpegStream","controller","AbortController","resolveInitialFrame","initialFrameTimer","resolveInitialFrameOnce","hasFrame","initialFrameReady","Promise","resolve","setTimeout","Set","Map","opts","createInterfaceMjpegHub","getDebug"],"mappings":";;;;;;;;;;;AAQA,MAAMA,yBAAyB;AAE/B,MAAMC,YAA2B,KAAO;AAiDjC,SAASC,gBACdC,GAAa,EACbC,QAAgB,EAChBC,KAAuB;IAEvB,MAAMC,MAAMD,MAAM,IAAI,CAAC,OAAO,CAACL,wBAAwB;IACvD,MAAMO,MAAMC,OAAO,IAAI,CAACF,KAAK;IAK7B,IAAIG,WAAWN,IAAI,KAAK,CAAC,CAAC,EAAE,EAAEC,SAAS,IAAI,CAAC;IAC5CK,WACEN,IAAI,KAAK,CAAC,CAAC,cAAc,EAAEE,MAAM,WAAW,IAAI,aAAa,IAAI,CAAC,KAClEI;IACFA,WAAWN,IAAI,KAAK,CAAC,CAAC,gBAAgB,EAAEI,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAKE;IACjEA,WAAWN,IAAI,KAAK,CAACI,QAAQE;IAC7BA,WAAWN,IAAI,KAAK,CAAC,WAAWM;IAChC,OAAOA;AACT;AAEA,SAASC,iBAAiBP,GAAa;IACrC,IAAI;QACFA,IAAI,GAAG;IACT,EAAE,OAAM,CAER;AACF;AAiBO,MAAMQ;IAaX,MAAM,cACJC,GAAY,EACZT,GAAa,EACbU,eAAgC,EAChCC,kBAAsC,EACpB;QAClB,OAAO,IAAI,CAAC,qBAAqB,CAC/BF,KACAT,KACAU,iBACAC,oBACA;IAEJ;IAMA,eAAqB;QACnB,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ;IACzC;IAMA,WAAiB;QACf,MAAMC,WAAW,IAAI,CAAC,QAAQ;QAC9B,IAAI,CAACA,UAAU;QACf,KAAK,MAAM,CAACC,YAAYb,IAAI,IAAIY,SAAS,SAAS,CAAE;YAClDA,SAAS,WAAW,CAAC,MAAM,CAACC;YAC5B,IAAI;gBACFb,IAAI,OAAO;YACb,EAAE,OAAM,CAER;QACF;QACAY,SAAS,SAAS,CAAC,KAAK;QACxB,IAAI,CAAC,oBAAoB,CAACA;IAC5B;IAEA,eAA6C;QAC3C,OAAO,IAAI,CAAC,QAAQ,EAAE;IACxB;IAEA,MAAc,sBACZH,GAAY,EACZT,GAAa,EACbU,eAAgC,EAChCC,kBAAsC,EACtCG,aAAsB,EACJ;QAClB,MAAMF,WAAW,IAAI,CAAC,mBAAmB,CAACF;QAC1C,IAAI,CAACE,UAAU,OAAO;QAEtB,MAAMG,kBAAkB,MAAMH,SAAS,eAAe;QACtD,IAAI,CAACG,mBAAmB,CAACH,SAAS,SAAS,EAAE;YAC3C,IAAI,CAAC,KAAK,CACR;YAEF,MAAMI,eAAeJ,SAAS,YAAY;YAC1C,IAAI,CAAC,oBAAoB,CAACA;YAC1B,IAAIE,iBAAiBE,cAAc;gBACjC,MAAMC,qBAAqB,MAAMN,mBAAmBK;gBACpD,IAAIC,oBACF,OAAO,IAAI,CAAC,qBAAqB,CAC/BR,KACAT,KACAiB,oBACAN,oBACA;YAGN;YACA,OAAO;QACT;QAEA,IAAI,CAAC,gBAAgB,CAACF,KAAKT,KAAKY;QAChC,OAAO;IACT;IAEQ,iBACNH,GAAY,EACZT,GAAa,EACbY,QAA0B,EACpB;QACN,MAAMX,WAAW;QACjB,IAAIiB,SAAS;QACb,IAAIC,WAAW;QAEf,MAAMC,gBAAgB;YACpB,IAAIF,QAAQ;YACZA,SAAS;YACT,IAAI,CAAC,iBAAiB,CAACN,UAAUC;QACnC;QAEA,MAAMA,aAAyB,CAACX;YAC9B,IAAIgB,QAAQ;YAIZ,IAAIC,UAAU;YACd,IAAI;gBACF,MAAMb,WAAWP,gBAAgBC,KAAKC,UAAUC;gBAChD,IAAI,CAACI,UAAU;oBACba,WAAW;oBACXnB,IAAI,IAAI,CAAC,SAAS;wBAChBmB,WAAW;oBACb;gBACF;YACF,EAAE,OAAOE,OAAO;gBACd,IAAI,CAAC,KAAK,CAAC,oCAAoCA;gBAC/CD;gBACA,IAAI;oBACFpB,IAAI,OAAO;gBACb,EAAE,OAAM,CAER;YACF;QACF;QAiBA,KAAK,MAAM,CAACsB,eAAeC,OAAO,IAAIX,SAAS,SAAS,CAAE;YACxDA,SAAS,WAAW,CAAC,MAAM,CAACU;YAC5BV,SAAS,SAAS,CAAC,MAAM,CAACU;YAC1Bf,iBAAiBgB;QACnB;QAEAX,SAAS,WAAW,CAAC,GAAG,CAACC;QACzBD,SAAS,SAAS,CAAC,GAAG,CAACC,YAAYb;QACnC,IAAIY,SAAS,SAAS,EAAE;YACtBY,aAAaZ,SAAS,SAAS;YAC/BA,SAAS,SAAS,GAAGa;QACvB;QACAhB,IAAI,EAAE,CAAC,SAASW;QAEhBpB,IAAI,SAAS,CACX,gBACA,CAAC,oCAAoC,EAAEC,UAAU;QAEnDD,IAAI,SAAS,CAAC,iBAAiB;QAC/BA,IAAI,SAAS,CAAC,cAAc;QAC5B,IAAI,CAAC,iBAAiB,CAACa,YAAYD,SAAS,SAAS;QAErD,IAAI,CAAC,KAAK,CAAC;IACb;IAwBQ,kBACNC,UAAsB,EACtBa,SAA2B,EACrB;QACNb,WAAWa;QACXb,WAAWa;IACb;IAEQ,oBACNhB,eAAgC,EACP;QACzB,MAAMiB,mBAAmBjB,gBAAgB,gBAAgB;QACzD,IAAI,AAA4B,cAA5B,OAAOiB,kBAAiC,OAAO;QAEnD,IAAI,IAAI,CAAC,QAAQ,EAAE,WAAWjB,iBAAiB;YAC7C,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE;gBAC3Bc,aAAa,IAAI,CAAC,QAAQ,CAAC,SAAS;gBACpC,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAGC;YAC5B;YACA,OAAO,IAAI,CAAC,QAAQ;QACtB;QAEA,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ;QAEvC,MAAMG,aAAa,IAAIC;QACvB,IAAIC;QACJ,IAAIC;QAEJ,MAAMC,0BAA0B,CAACC;YAC/B,IAAI,CAACH,qBAAqB;YAC1B,IAAIC,mBAAmB;gBACrBP,aAAaO;gBACbA,oBAAoBN;YACtB;YACAK,oBAAoBG;YACpBH,sBAAsBL;QACxB;QAEA,MAAMS,oBAAoB,IAAIC,QAAiB,CAACC;YAC9CN,sBAAsBM;YACtBL,oBAAoBM,WAAW;gBAC7BL,wBAAwB;YAC1B,GAAG,IAAI,CAAC,IAAI,CAAC,qBAAqB;QACpC;QAEA,MAAMpB,WAA6B;YACjC,QAAQF;YACRkB;YACA,iBAAiBM;YACjB,aAAa,IAAII;YACjB,WAAW,IAAIC;QACjB;QACA,IAAI,CAAC,QAAQ,GAAG3B;QAEV;YACJ,IAAI;gBACFA,SAAS,MAAM,GACZ,MAAMe,iBAAiB,IAAI,CAACjB,iBAAiB;oBAC5C,QAAQkB,WAAW,MAAM;oBACzB,SAAS,CAAC1B;wBACR,IAAI0B,WAAW,MAAM,CAAC,OAAO,EAAE;wBAC/BhB,SAAS,SAAS,GAAGV;wBACrB8B,wBAAwB;wBACxB,KAAK,MAAMnB,cAAcD,SAAS,WAAW,CAC3CC,WAAWX;oBAEf;oBACA,SAAS,CAACmB;wBACR,IAAI,CAAC,KAAK,CAAC,uCAAuCA;wBAOlD,IAAI,CAAC,oBAAoB,CAACT;oBAC5B;gBACF,MAAOa;YACX,EAAE,OAAOJ,OAAO;gBACd,IAAI,CAAC,KAAK,CAAC,4CAA4CA;gBACvDT,SAAS,YAAY,GAAGS;gBACxBW,wBAAwB;gBACxB,IAAI,CAAC,oBAAoB,CAACpB;YAC5B;QACF;QAEA,OAAOA;IACT;IAEQ,qBAAqBA,QAA2B,EAAQ;QAC9D,IAAI,CAACA,UAAU;QACf,IAAIA,SAAS,SAAS,EAAE;YACtBY,aAAaZ,SAAS,SAAS;YAC/BA,SAAS,SAAS,GAAGa;QACvB;QAIA,KAAK,MAAM,GAAGzB,IAAI,IAAIY,SAAS,SAAS,CACtCL,iBAAiBP;QAEnBY,SAAS,SAAS,CAAC,KAAK;QACxBA,SAAS,WAAW,CAAC,KAAK;QAC1BA,SAAS,UAAU,CAAC,KAAK;QACzBuB,QAAQ,OAAO,CAACvB,SAAS,MAAM,EAAE,UAAU,KAAK,CAAC,CAACS;YAChD,IAAI,CAAC,KAAK,CAAC,oCAAoCA;QACjD;QACA,IAAI,IAAI,CAAC,QAAQ,KAAKT,UACpB,IAAI,CAAC,QAAQ,GAAGa;IAEpB;IAEQ,kBACNb,QAA0B,EAC1BC,UAAsB,EAChB;QACND,SAAS,WAAW,CAAC,MAAM,CAACC;QAC5BD,SAAS,SAAS,CAAC,MAAM,CAACC;QAC1B,IAAID,SAAS,WAAW,CAAC,IAAI,GAAG,KAAKA,SAAS,SAAS,EAAE;QACzDA,SAAS,SAAS,GAAGyB,WAAW;YAC9BzB,SAAS,SAAS,GAAGa;YACrB,IAAIb,AAA8B,MAA9BA,SAAS,WAAW,CAAC,IAAI,EAC3B,IAAI,CAAC,oBAAoB,CAACA;QAE9B,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU;IACzB;IA9TA,YAA6B4B,IAA8B,CAAE;;QAH7D,uBAAQ,YAAR;QACA,uBAAiB,SAAjB;aAE6BA,IAAI,GAAJA;QAC3B,IAAI,CAAC,KAAK,GAAGA,KAAK,KAAK,IAAI1C;IAC7B;AA6TF;AAMO,SAAS2C,wBACdD,IAAyE;IAEzE,OAAO,IAAIhC,kBAAkB;QAC3B,GAAGgC,IAAI;QACP,OAAOA,KAAK,KAAK,IAAIE,SAAS;IAChC;AACF"}