import originalFetch from 'isomorphic-unfetch'; import retry from 'fetch-retry'; import { nanoid } from 'nanoid'; import { Options, TelemetryData } from './types'; import { getAnonymousProjectId } from './anonymous-id'; const URL = 'https://storybook.js.org/event-log'; const fetch = retry(originalFetch); let tasks: Promise[] = []; // getStorybookMetadata -> packagejson + Main.js // event specific data: sessionId, ip, etc.. // send telemetry const sessionId = nanoid(); export async function sendTelemetry( data: TelemetryData, options: Partial = { retryDelay: 1000, immediate: false } ) { // We use this id so we can de-dupe events that arrive at the index multiple times due to the // use of retries. There are situations in which the request "5xx"s (or times-out), but // the server actually gets the request and stores it anyway. // flatten the data before we send it const { payload, metadata, ...rest } = data; const context = { anonymousId: getAnonymousProjectId(), inCI: process.env.CI === 'true', }; const eventId = nanoid(); const body = { ...rest, eventId, sessionId, metadata, payload, context }; let request: Promise; try { request = fetch(URL, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, retries: 3, retryOn: [503, 504], retryDelay: (attempt: number) => 2 ** attempt * (typeof options?.retryDelay === 'number' && !Number.isNaN(options?.retryDelay) ? options.retryDelay : 1000), }); tasks.push(request); if (options.immediate) { await Promise.all(tasks); } else { await request; } } catch (err) { // } finally { tasks = tasks.filter((task) => task !== request); } }