<commit_before>import { location, fetch } from 'global';
import { logger } from '@storybook/client-logger';
import dedent from 'ts-dedent';
import {
  transformStoriesRawToStoriesHash,
  StoriesRaw,
  StoryInput,
  StoriesHash,
} from '../lib/stories';

import { ModuleFn } from '../index';

export interface SubState {
  refs: Refs;
}

type Versions = Record<string, string>;

export type SetRefData = Omit<ComposedRef, 'stories'> & {
  stories?: StoriesRaw;
};

export interface SubAPI {
  findRef: (source: string) => ComposedRef;
  setRef: (id: string, data: SetRefData, ready?: boolean) => void;
  getRefs: () => Refs;
  checkRef: (ref: SetRefData) => Promise<void>;
  changeRefVersion: (id: string, url: string) => void;
  changeRefState: (id: string, ready: boolean) => void;
}

export type StoryMapper = (ref: ComposedRef, story: StoryInput) => StoryInput;
export interface ComposedRef {
  id: string;
  title?: string;
  url: string;
  type?: 'auto-inject' | 'unknown' | 'lazy';
  stories: StoriesHash;
  versions?: Versions;
  authUrl?: string;
  ready?: boolean;
  error?: any;
}

export type Refs = Record<string, ComposedRef>;
export type RefId = string;
export type RefUrl = string;

// eslint-disable-next-line no-useless-escape
const findFilename = /(\/((?:[^\/]+?)\.[^\/]+?)|\/)$/;

const allSettled = (promises: Promise<any>[]) =>
  Promise.all(
    promises.map((promise, i) =>
      promise.then(
        (r) => r.ok,
        () => false
      )
    )
  );

export const getSourceType = (source: string) => {
  const { origin: localOrigin, pathname: localPathname } = location;
  const { origin: sourceOrigin, pathname: sourcePathname } = new URL(source);

  const localFull = `${localOrigin + localPathname}`.replace(findFilename, '');
  const sourceFull = `${sourceOrigin + sourcePathname}`.replace(findFilename, '');

  if (localFull === sourceFull) {
    return ['local', sourceFull];
  }
  if (source) {
    return ['external', sourceFull];
  }
  return [null, null];
};

export const defaultStoryMapper: StoryMapper = (b, a) => {
  return { ...a, kind: a.kind.replace('|', '/') };
};

const addRefIds = (input: StoriesHash, ref: ComposedRef): StoriesHash => {
  return Object.entries(input).reduce((acc, [id, item]) => {
    return { ...acc, [id]: { ...item, refId: ref.id } };
  }, {} as StoriesHash);
};

const map = (
  input: StoriesRaw,
  ref: ComposedRef,
  options: { storyMapper?: StoryMapper }
): StoriesRaw => {
  const { storyMapper } = options;
  if (storyMapper) {
    return Object.entries(input).reduce((acc, [id, item]) => {
      return { ...acc, [id]: storyMapper(ref, item) };
    }, {} as StoriesRaw);
  }
  return input;
};

export const init: ModuleFn = ({ store, provider, fullAPI }) => {
  const api: SubAPI = {
    findRef: (source) => {
      const refs = api.getRefs();

      return Object.values(refs).find(({ url }) => url.match(source));
    },
    changeRefVersion: (id, url) => {
      const previous = api.getRefs()[id];
      const ref = { ...previous, stories: {}, url } as SetRefData;

      api.checkRef(ref);
    },
    changeRefState: (id, ready) => {
      const refs = api.getRefs();
      store.setState({
        refs: {
          ...refs,
          [id]: { ...refs[id], ready },
        },
      });
    },
    checkRef: async (ref) => {
      const { id, url } = ref;

      const handler = async (response: Response) => {
        if (response) {
          const { ok } = response;

          if (ok) {
            return response.json().catch((error: Error) => ({ error }));
          }
        } else {
          logger.warn('an auto-injected ref threw a cors-error');
        }

        return false;
      };

      const loadedData: { error?: Error; stories?: StoriesRaw } = {};

      const [included, omitted] = await allSettled([
        fetch(`${url}/iframe.html`, {
          credentials: 'include',
        }),
        fetch(`${url}/iframe.html`, {
          credentials: 'omit',
        }),
      ]);

      if (!included && !omitted) {
        loadedData.error = {
          message: dedent`
            Error: Loading of ref failed
              at fetch (lib/api/src/modules/refs.ts)

            URL: ${url}

            Tried to load this ref without and with credentials,
            but there's likely a CORS error happening.

            Please check your dev-tools network tab.
          `,
        } as Error;
      } else {
        const credentials = !omitted ? 'include' : 'omit';

        const [stories, metadata] = await Promise.all([
          fetch(`${url}/stories.json`, {
            headers: {
              Accept: 'application/json',
            },
            credentials,
          })
            .catch(() => false)
            .then(handler),
          fetch(`${url}/metadata.json`, {
            headers: {
              Accept: 'application/json',
            },
            credentials,
            cache: 'no-cache',
          })
            .catch(() => false)
            .then(handler),
        ]);

        Object.assign(loadedData, { ...stories, ...metadata });
      }
<commit_msg>so I fetch stories.json WITH credentials, WITHOUT credentials, and I try to get iframe.html<commit_after>import { location, fetch } from 'global';
import { logger } from '@storybook/client-logger';
import dedent from 'ts-dedent';
import {
  transformStoriesRawToStoriesHash,
  StoriesRaw,
  StoryInput,
  StoriesHash,
} from '../lib/stories';

import { ModuleFn } from '../index';

export interface SubState {
  refs: Refs;
}

type Versions = Record<string, string>;

export type SetRefData = Omit<ComposedRef, 'stories'> & {
  stories?: StoriesRaw;
};

export interface SubAPI {
  findRef: (source: string) => ComposedRef;
  setRef: (id: string, data: SetRefData, ready?: boolean) => void;
  getRefs: () => Refs;
  checkRef: (ref: SetRefData) => Promise<void>;
  changeRefVersion: (id: string, url: string) => void;
  changeRefState: (id: string, ready: boolean) => void;
}

export type StoryMapper = (ref: ComposedRef, story: StoryInput) => StoryInput;
export interface ComposedRef {
  id: string;
  title?: string;
  url: string;
  type?: 'auto-inject' | 'unknown' | 'lazy';
  stories: StoriesHash;
  versions?: Versions;
  authUrl?: string;
  ready?: boolean;
  error?: any;
}

export type Refs = Record<string, ComposedRef>;
export type RefId = string;
export type RefUrl = string;

// eslint-disable-next-line no-useless-escape
const findFilename = /(\/((?:[^\/]+?)\.[^\/]+?)|\/)$/;

const allSettled = (promises: Promise<any>[]) =>
  Promise.all(
    promises.map((promise, i) =>
      promise.then(
        (r) => (r.ok ? r : false),
        () => false
      )
    )
  );

export const getSourceType = (source: string) => {
  const { origin: localOrigin, pathname: localPathname } = location;
  const { origin: sourceOrigin, pathname: sourcePathname } = new URL(source);

  const localFull = `${localOrigin + localPathname}`.replace(findFilename, '');
  const sourceFull = `${sourceOrigin + sourcePathname}`.replace(findFilename, '');

  if (localFull === sourceFull) {
    return ['local', sourceFull];
  }
  if (source) {
    return ['external', sourceFull];
  }
  return [null, null];
};

export const defaultStoryMapper: StoryMapper = (b, a) => {
  return { ...a, kind: a.kind.replace('|', '/') };
};

const addRefIds = (input: StoriesHash, ref: ComposedRef): StoriesHash => {
  return Object.entries(input).reduce((acc, [id, item]) => {
    return { ...acc, [id]: { ...item, refId: ref.id } };
  }, {} as StoriesHash);
};

const map = (
  input: StoriesRaw,
  ref: ComposedRef,
  options: { storyMapper?: StoryMapper }
): StoriesRaw => {
  const { storyMapper } = options;
  if (storyMapper) {
    return Object.entries(input).reduce((acc, [id, item]) => {
      return { ...acc, [id]: storyMapper(ref, item) };
    }, {} as StoriesRaw);
  }
  return input;
};

export const init: ModuleFn = ({ store, provider, fullAPI }) => {
  const api: SubAPI = {
    findRef: (source) => {
      const refs = api.getRefs();

      return Object.values(refs).find(({ url }) => url.match(source));
    },
    changeRefVersion: (id, url) => {
      const previous = api.getRefs()[id];
      const ref = { ...previous, stories: {}, url } as SetRefData;

      api.checkRef(ref);
    },
    changeRefState: (id, ready) => {
      const refs = api.getRefs();
      store.setState({
        refs: {
          ...refs,
          [id]: { ...refs[id], ready },
        },
      });
    },
    checkRef: async (ref) => {
      const { id, url } = ref;

      const loadedData: { error?: Error; stories?: StoriesRaw } = {};

      const [included, omitted, iframe] = await allSettled([
        fetch(`${url}/stories.json`, {
          headers: {
            Accept: 'application/json',
          },
          credentials: 'include',
        }),
        fetch(`${url}/stories.json`, {
          headers: {
            Accept: 'application/json',
          },
          credentials: 'omit',
        }),
        fetch(`${url}/iframe.html`, {
          credentials: 'omit',
        }),
      ]);

      const handle = async (request: Promise<Response> | false) => {
        if (request) {
          return Promise.resolve(request)
            .then((response) => (response.ok ? response.json() : {}))
            .catch((error) => ({ error }));
        }
        return {};
      };

      if (!included && !omitted && !iframe) {
        loadedData.error = {
          message: dedent`
            Error: Loading of ref failed
              at fetch (lib/api/src/modules/refs.ts)

            URL: ${url}

            We weren't able to load the above URL,
            it's possible a CORS error happened.

            Please check your dev-tools network tab.
          `,
        } as Error;
      } else {
        const credentials = !omitted ? 'include' : 'omit';

        const [stories, metadata] = await Promise.all([
          handle(omitted || included),
          handle(
            fetch(`${url}/metadata.json`, {
              headers: {
                Accept: 'application/json',
              },
              credentials,
              cache: 'no-cache',
            })
          ),
        ]);

        Object.assign(loadedData, { ...stories, ...metadata });
      }

{'fraction_non_alphanumeric': 0.16402219410104157, 'fraction_numerical': 0.0, 'mean_word_length': 2.387405209363666, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6408916', 'n_tokens_mistral': 3174, 'n_tokens_neox': 3126, 'n_words': 797}