async fetchAttributions_(frameState) {
    if (
      frameState.viewHints[ViewHint.ANIMATING] ||
      frameState.viewHints[ViewHint.INTERACTING] ||
      frameState.animate
    ) {
      return this.previousViewportAttribution_;
    }
    const [west, south] = toLonLat(
      getBottomLeft(frameState.extent),
      frameState.viewState.projection,
    );
    const [east, north] = toLonLat(
      getTopRight(frameState.extent),
      frameState.viewState.projection,
    );
    const tileGrid = this.getTileGrid();
    const zoom = tileGrid.getZForResolution(
      frameState.viewState.resolution,
      this.zDirection,
    );
    const viewportExtent = `zoom=${zoom}&north=${north}&south=${south}&east=${east}&west=${west}`;
    // check if the extent or zoom has actually changed to avoid unnecessary requests
    if (this.previousViewportExtent_ == viewportExtent) {
      return this.previousViewportAttribution_;
    }
    this.previousViewportExtent_ = viewportExtent;
    const session = this.sessionTokenValue_;
    const key = this.apiKey_;
    const url = `${attributionUrl}?session=${session}&key=${key}&${viewportExtent}`;
    this.previousViewportAttribution_ = await fetch(url)
      .then((response) => response.json())
      .then((json) => json.copyright);

    return this.previousViewportAttribution_;
  }
@param {import('../Map.js').FrameState} frameState The frame state.
@return {Promise<string>} The attributions.
@private