export function plugin()

in desktop/plugins/public/fresco/index.tsx [71:397]


export function plugin(client: PluginClient<Events, Methods>) {
  const selectedSurfaces = createState<Set<string>>(
    new Set([surfaceDefaultText]),
  );
  const currentSelectedImage = createState<ImageId | null>(null);
  const isDebugOverlayEnabled = createState<boolean>(false);
  const isAutoRefreshEnabled = createState<boolean>(false);
  const currentImages = createState<ImagesList>([]);
  const coldStartFilter = createState<boolean>(false);
  const imagePool = createState<ImagePool | null>(null);

  const surfaceList = createState<Set<string>>(new Set(), {
    persist: 'surfaceList',
  });
  const images = createState<ImagesList>([], {persist: 'images'});
  const events = createState<Array<ImageEventWithId>>([], {persist: 'events'});
  const imagesMap = createState<ImagesMap>({}, {persist: 'imagesMap'});
  const isLeakTrackingEnabled = createState<boolean>(false, {
    persist: 'isLeakTrackingEnabled',
  });
  const showDiskImages = createState<boolean>(false, {
    persist: 'showDiskImages',
  });
  const nextEventId = createState<number>(0, {persist: 'nextEventId'});

  client.onConnect(() => {
    init();
  });

  client.onDestroy(() => {
    imagePool?.get()?.clear();
  });

  client.onMessage('closeable_reference_leak_event', (event) => {
    if (isLeakTrackingEnabled) {
      client.showNotification({
        id: event.identityHashCode,
        title: `Leaked CloseableReference: ${event.className}`,
        message: (
          <Layout.Container>
            <Typography.Text>CloseableReference leaked for </Typography.Text>
            <Typography.Text code>{event.className}</Typography.Text>
            <Typography.Text>
              (identity hashcode: {event.identityHashCode}).
            </Typography.Text>
            <Typography.Text strong>Stacktrace:</Typography.Text>
            <Typography.Text code>
              {event.stacktrace || '<unavailable>'}
            </Typography.Text>
          </Layout.Container>
        ),
        severity: 'error',
        category: 'closeablereference_leak',
      });
    }
  });

  client.onExport(async () => {
    const [responseImages, responseEvents] = await Promise.all([
      client.send('listImages', {showDiskImages: showDiskImages.get()}),
      client.send('getAllImageEventsInfo', {}),
    ]);
    const levels: ImagesList = responseImages.levels;
    const newEvents: Array<ImageEventWithId> = responseEvents.events;

    images.set([...images.get(), ...levels]);

    newEvents.forEach((event: ImageEventWithId, index) => {
      if (!event) {
        return;
      }
      const {attribution} = event;
      if (
        attribution &&
        attribution instanceof Array &&
        attribution.length > 0
      ) {
        const surface = attribution[0] ? attribution[0].trim() : undefined;
        if (surface && surface.length > 0) {
          surfaceList.set(new Set([...surfaceList.get(), surface]));
        }
      }
      events.set([{...event, eventId: index}, ...events.get()]);
    });
    const idSet: Set<string> = levels.reduce((acc, level: CacheInfo) => {
      level.imageIds.forEach((id) => {
        acc.add(id);
      });
      return acc;
    }, new Set<string>());
    const imageDataList: Array<ImageData> = [];
    for (const id of idSet) {
      try {
        const imageData: ImageData = await client.send('getImage', {
          imageId: id,
        });
        imageDataList.push(imageData);
      } catch (e) {
        console.error('[fresco] getImage failed:', e);
      }
    }

    const imagesMapCopy = {...imagesMap.get()};
    imageDataList.forEach((data: ImageData) => {
      imagesMapCopy[data.imageId] = data;
    });
    imagesMap.set(imagesMapCopy);
  });

  client.onMessage('debug_overlay_event', (event) => {
    isDebugOverlayEnabled.set(event.enabled);
  });

  client.onMessage('events', (event) => {
    debugLog('Received events', event);
    const {attribution} = event;
    if (attribution instanceof Array && attribution.length > 0) {
      const surface = attribution[0] ? attribution[0].trim() : undefined;
      if (surface && surface.length > 0) {
        surfaceList.update((draft) => (draft = new Set([...draft, surface])));
      }
    }
    events.update((draft) => {
      draft.unshift({
        eventId: nextEventId.get(),
        ...event,
      });
    });

    nextEventId.set(nextEventId.get() + 1);
  });

  function onClear(type: string) {
    client.send('clear', {type});
    setTimeout(() => updateCaches('onClear'), 1000);
  }

  function onTrimMemory() {
    client.send('trimMemory', {});
    setTimeout(() => updateCaches('onTrimMemory'), 1000);
  }

  function onEnableDebugOverlay(enabled: boolean) {
    client.send('enableDebugOverlay', {enabled});
  }

  function onEnableAutoRefresh(enabled: boolean) {
    isAutoRefreshEnabled.set(enabled);

    if (enabled) {
      // Delay the call just enough to allow the state change to complete.
      setTimeout(() => onAutoRefresh());
    }
  }

  function onAutoRefresh() {
    updateCaches('auto-refresh');
    if (isAutoRefreshEnabled.get()) {
      setTimeout(() => onAutoRefresh(), 1000);
    }
  }

  function getImage(imageId: string) {
    if (!client.isConnected) {
      debugLog(`Cannot fetch image ${imageId}: disconnected`);
      return;
    }
    debugLog('<- getImage requested for ' + imageId);
    client
      .send('getImage', {imageId})
      .then((image: ImageData) => {
        debugLog('-> getImage ' + imageId + ' returned');
        imagePool.get()?._fetchCompleted(image);
      })
      .catch((e) => console.error('[fresco] getImage failed:', e));
  }

  function onImageSelected(selectedImage: ImageId) {
    currentSelectedImage.set(selectedImage);
  }

  function onSurfaceChange(surfaces: Set<string>) {
    updateImagesOnUI(images.get(), surfaces, coldStartFilter.get());
  }

  function onColdStartChange(checked: boolean) {
    updateImagesOnUI(images.get(), selectedSurfaces.get(), checked);
  }

  function onTrackLeaks(checked: boolean) {
    client.logger.track('usage', 'fresco:onTrackLeaks', {
      enabled: checked,
    });

    isLeakTrackingEnabled.set(checked);
  }

  function onShowDiskImages(checked: boolean) {
    client.logger.track('usage', 'fresco:onShowDiskImages', {
      enabled: checked,
    });

    showDiskImages.set(checked);
    updateCaches('refresh');
  }

  function init() {
    debugLog('init()');
    if (client.isConnected) {
      updateCaches('init');
    } else {
      debugLog(`not connected)`);
    }
    imagePool.set(
      new ImagePool(getImage, (images: ImagesMap) => imagesMap.set(images)),
    );

    const filteredImages = filterImages(
      images.get(),
      events.get(),
      selectedSurfaces.get(),
      coldStartFilter.get(),
    );

    images.set(filteredImages);
  }

  function filterImages(
    images: ImagesList,
    events: Array<ImageEventWithId>,
    surfaces: Set<string>,
    coldStart: boolean,
  ): ImagesList {
    if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) {
      return images;
    }

    const imageList = images.map((image: CacheInfo) => {
      const imageIdList = image.imageIds.filter((imageID) => {
        const filteredEvents = events.filter((event: ImageEventWithId) => {
          const output =
            event.attribution &&
            event.attribution.length > 0 &&
            event.imageIds &&
            event.imageIds.includes(imageID);

          if (surfaces.has(surfaceDefaultText)) {
            return output && coldStart && event.coldStart;
          }

          return (
            (!coldStart || (coldStart && event.coldStart)) &&
            output &&
            surfaces.has(event.attribution[0])
          );
        });
        return filteredEvents.length > 0;
      });
      return {...image, imageIds: imageIdList};
    });
    return imageList;
  }

  function updateImagesOnUI(
    newImages: ImagesList,
    surfaces: Set<string>,
    coldStart: boolean,
  ) {
    const filteredImages = filterImages(
      newImages,
      events.get(),
      surfaces,
      coldStart,
    );

    selectedSurfaces.set(surfaces);
    images.set(filteredImages);
    coldStartFilter.set(coldStart);
  }

  function updateCaches(reason: string) {
    debugLog('Requesting images list (reason=' + reason + ')');
    client
      .send('listImages', {
        showDiskImages: showDiskImages.get(),
      })
      .then((response: ImagesListResponse) => {
        response.levels.forEach((data) =>
          imagePool?.get()?.fetchImages(data.imageIds),
        );
        images.set(response.levels);
        updateImagesOnUI(
          images.get(),
          selectedSurfaces.get(),
          coldStartFilter.get(),
        );
      })
      .catch((e) => console.error('[fresco] listImages failed:', e));
  }

  return {
    selectedSurfaces,
    currentSelectedImage,
    isDebugOverlayEnabled,
    isAutoRefreshEnabled,
    currentImages,
    coldStartFilter,
    surfaceList,
    images,
    events,
    imagesMap,
    isLeakTrackingEnabled,
    showDiskImages,
    nextEventId,
    imagePool,
    onSurfaceChange,
    onColdStartChange,
    onClear,
    onTrimMemory,
    updateCaches,
    onEnableDebugOverlay,
    onEnableAutoRefresh,
    onImageSelected,
    onTrackLeaks,
    onShowDiskImages,
  };
}