diff --git a/.changeset/breezy-balloons-ring.md b/.changeset/breezy-balloons-ring.md new file mode 100644 index 00000000000..68e703501c4 --- /dev/null +++ b/.changeset/breezy-balloons-ring.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/service-utils": patch +--- + +[service-utils] Helper to call client usageV2 reporting endpoint diff --git a/packages/service-utils/src/cf-worker/usageV2.ts b/packages/service-utils/src/cf-worker/usageV2.ts index b9fcbd0ec37..74baec5be29 100644 --- a/packages/service-utils/src/cf-worker/usageV2.ts +++ b/packages/service-utils/src/cf-worker/usageV2.ts @@ -1,47 +1,51 @@ import type { UsageV2Event, UsageV2Source } from "../core/usageV2.js"; +type UsageV2Options = { + environment: "development" | "production"; + source: UsageV2Source; +} & ( + | { serviceKey: string; thirdwebClientId?: never; thirdwebSecretKey?: never } + | { serviceKey?: never; thirdwebClientId: string; thirdwebSecretKey?: never } + | { serviceKey?: never; thirdwebClientId?: never; thirdwebSecretKey: string } +); + /** - * Send events to Kafka. - * This method may throw. To call this non-blocking: + * Send usageV2 events from either internal services or public clients. + * + * Exactly one authentication method must be provided: + * - serviceKey: for internal services + * - thirdwebClientId: for public clients (MUST be the user's project) + * - thirdwebSecretKey: for public clients (MUST be the user's project) * + * This method may throw. To call this non-blocking: * ```ts - * void sendUsageV2Events(events, { - * environment: "production", - * serviceKey: "..." - * }).catch(console.error) + * void sendUsageV2Events(...).catch((e) => console.error(e)) * ``` - * - * @param events - The events to send. - * @param options.environment - The environment the service is running in. - * @param options.serviceKey - The service key required for authentication. */ export async function sendUsageV2Events( events: UsageV2Event[], - options: { - environment: "development" | "production"; - source: UsageV2Source; - serviceKey?: string; - }, + options: UsageV2Options, ): Promise { const baseUrl = options.environment === "production" ? "https://u.thirdweb.com" : "https://u.thirdweb-dev.com"; - // Unauthed calls are routed to the /client path + // Determine endpoint and auth header based on provided credentials. let url: string; - let headers: HeadersInit; + const headers: HeadersInit = { "Content-Type": "application/json" }; + if (options.serviceKey) { url = `${baseUrl}/usage-v2/${options.source}`; - headers = { - "Content-Type": "application/json", - "x-service-api-key": options.serviceKey, - }; - } else { + headers["x-service-api-key"] = options.serviceKey; + } else if (options.thirdwebSecretKey) { url = `${baseUrl}/usage-v2/${options.source}/client`; - headers = { - "Content-Type": "application/json", - }; + headers["x-secret-key"] = options.thirdwebSecretKey; + } else if (options.thirdwebClientId) { + url = `${baseUrl}/usage-v2/${options.source}/client`; + headers["x-client-id"] = options.thirdwebClientId; + } else { + throw new Error("[UsageV2] No authentication method provided."); } const resp = await fetch(url, { @@ -52,7 +56,7 @@ export async function sendUsageV2Events( if (!resp.ok) { throw new Error( - `[UsageV2] unexpected response ${resp.status}: ${await resp.text()}`, + `[UsageV2] Unexpected response ${resp.status}: ${await resp.text()}`, ); } resp.body?.cancel();