diff --git a/packages/actor-core-cli/src/utils/rivet-api.ts b/packages/actor-core-cli/src/utils/rivet-api.ts index 76e8a3e2f..eeed91d2d 100644 --- a/packages/actor-core-cli/src/utils/rivet-api.ts +++ b/packages/actor-core-cli/src/utils/rivet-api.ts @@ -1,4 +1,5 @@ import { z, type ZodTypeAny } from "zod"; +import { httpUserAgent } from "actor-core/utils"; export async function getServiceToken( api: ReturnType, @@ -75,6 +76,7 @@ export function createRivetApi(endpoint: string, accessToken: string) { headers: { ...opts.headers, "Content-Type": "application/json", + "User-Agent": httpUserAgent(), Authorization: `Bearer ${accessToken}`, }, }); diff --git a/packages/actor-core/src/client/actor-conn.ts b/packages/actor-core/src/client/actor-conn.ts index 3aea77237..f089dede8 100644 --- a/packages/actor-core/src/client/actor-conn.ts +++ b/packages/actor-core/src/client/actor-conn.ts @@ -5,6 +5,7 @@ import type * as wsToServer from "@/actor/protocol/message/to-server"; import type { Encoding } from "@/actor/protocol/serde"; import { importEventSource } from "@/common/eventsource"; import { MAX_CONN_PARAMS_SIZE } from "@/common/network"; +import { httpUserAgent } from "@/utils"; import { assertUnreachable, stringifyError } from "@/common/utils"; import { importWebSocket } from "@/common/websocket"; import type { ActorQuery } from "@/manager/protocol/query"; @@ -686,6 +687,9 @@ enc const messageSerialized = this.#serialize(message); const res = await fetch(url, { method: "POST", + headers: { + "User-Agent": httpUserAgent(), + }, body: messageSerialized, }); @@ -845,4 +849,4 @@ enc */ export type ActorConn = ActorConnRaw & - ActorDefinitionRpcs; \ No newline at end of file + ActorDefinitionRpcs; diff --git a/packages/actor-core/src/client/utils.ts b/packages/actor-core/src/client/utils.ts index 38961781a..ca3b660a3 100644 --- a/packages/actor-core/src/client/utils.ts +++ b/packages/actor-core/src/client/utils.ts @@ -1,5 +1,6 @@ import { deserialize } from "@/actor/protocol/serde"; import { assertUnreachable, stringifyError } from "@/common/utils"; +import { httpUserAgent } from "@/utils"; import { Encoding } from "@/mod"; import * as cbor from "cbor-x"; import { ActorError, HttpRequestError } from "./errors"; @@ -62,11 +63,14 @@ export async function sendHttpRequest< // Make the HTTP request response = await fetch(opts.url, { method: opts.method, - headers: contentType - ? { - "Content-Type": contentType, - } - : {}, + headers: { + "User-Agent": httpUserAgent(), + ...(contentType + ? { + "Content-Type": contentType, + } + : {}), + }, body: bodyData, }); } catch (error) { diff --git a/packages/actor-core/src/utils.ts b/packages/actor-core/src/utils.ts index 9bd5bb45d..16dee2629 100644 --- a/packages/actor-core/src/utils.ts +++ b/packages/actor-core/src/utils.ts @@ -1 +1,24 @@ export { assertUnreachable } from "./common/utils"; +import pkgJson from "../package.json" with { type: "json" }; + +export const VERSION = pkgJson.version; + +let _userAgent: string | undefined = undefined; + +export function httpUserAgent(): string { + // Return cached value if already initialized + if (_userAgent !== undefined) { + return _userAgent; + } + + // Library + let userAgent = `ActorCore/${VERSION}`; + + // Navigator + const navigatorObj = typeof navigator !== "undefined" ? navigator : undefined; + if (navigatorObj?.userAgent) userAgent += ` ${navigatorObj.userAgent}`; + + _userAgent = userAgent; + + return userAgent; +} diff --git a/packages/platforms/rivet/src/rivet-client.ts b/packages/platforms/rivet/src/rivet-client.ts index 188a9f5bb..073ee2870 100644 --- a/packages/platforms/rivet/src/rivet-client.ts +++ b/packages/platforms/rivet/src/rivet-client.ts @@ -1,3 +1,5 @@ +import { httpUserAgent } from "actor-core/utils"; + export interface RivetClientConfig { endpoint: string; token: string; @@ -23,6 +25,7 @@ export async function rivetRequest( method, headers: { "Content-Type": "application/json", + "User-Agent": httpUserAgent(), Authorization: `Bearer ${config.token}`, }, body: body ? JSON.stringify(body) : undefined,