Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-balloons-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/service-utils": patch
---

[service-utils] Helper to call client usageV2 reporting endpoint
56 changes: 30 additions & 26 deletions packages/service-utils/src/cf-worker/usageV2.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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, {
Expand All @@ -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();
Expand Down
Loading