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/x-client-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tesla-fleet-api": minor
---

A new `client` option names your application, and is sent as the `X-Client` header on every request. `X-Library` is unchanged.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ await site.live_status();

Replies keep the service's `{ response }` envelope. `api.vehicles.byVin` and `api.energySites.byId` are `Map`s of what each factory created. More: [vehicles](docs/fleet_api_vehicles.md), [energy sites](docs/fleet_api_energy_sites.md).

`accessToken` may be a function returning `Promise<string | null>`, which is called for every request. Other options are `fetch`, `logger`, `debug` and `timeout` (milliseconds).
`accessToken` may be a function returning `Promise<string | null>`, which is called for every request. Other options are `fetch`, `logger`, `debug`, `timeout` (milliseconds) and `client`, a short name for your application sent as the `X-Client` header on every request. It must be printable ASCII, or the constructor throws.

## Signed commands

Expand Down
11 changes: 11 additions & 0 deletions src/teslafleetapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export default class TeslaFleetApi {
timeout: number | null;
/** Transport identity for debug logging; Teslemetry and Tessie override this. */
readonly _transport_name: string = "fleet";
/** Sent as `X-Client` on every request when set. */
client: string | null;
#fetch: typeof fetch | null;
// Native private fields, behind accessors: a key must not be reachable by
// walking the instance, e.g. in a structured log.
Expand All @@ -77,11 +79,17 @@ export default class TeslaFleetApi {
/** Used for every request in place of the global `fetch`. */
fetch?: typeof fetch;
timeout?: number;
/** A short name for the application, sent as `X-Client` on every request. */
client?: string;
}) {
this.debug = !!options.debug;
this.logger = options.logger ?? null;
this.#fetch = options.fetch ?? null;
this.timeout = options.timeout ?? null;
if (options.client && !/^[\x20-\x7e]+$/.test(options.client)) {
throw new Error("client must contain only printable ASCII characters.");
}
this.client = options.client || null;
this.accessToken = options.accessToken ?? null;

if (options.server) {
Expand Down Expand Up @@ -221,6 +229,9 @@ export default class TeslaFleetApi {
Authorization: `Bearer ${accessToken}`,
"X-Library": `node tesla_fleet_api ${__VERSION__}`,
};
if (this.client) {
headers["X-Client"] = this.client;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

// Remove null and undefined values from params and json
const query = new URLSearchParams();
Expand Down
2 changes: 2 additions & 0 deletions src/teslemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export type TeslemetryOptions = {
logger?: Logger;
fetch?: typeof fetch;
timeout?: number;
/** A short name for the application, sent as `X-Client` on every request. */
client?: string;
};

/** Teslemetry's OAuth dynamic client registration endpoint. */
Expand Down
2 changes: 2 additions & 0 deletions src/tessie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type TessieOptions = {
logger?: Logger;
fetch?: typeof fetch;
timeout?: number;
/** A short name for the application, sent as `X-Client` on every request. */
client?: string;
/** Default for each command's `wait_for_completion`. */
wait_for_completion?: boolean;
/** Default for each command's `max_attempts`. */
Expand Down
17 changes: 17 additions & 0 deletions test/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ describe("_request", () => {
expect(headersOf(fetch).get("X-Library")).toBe(`node tesla_fleet_api ${version}`);
});

it("sends X-Client only when client is set", async () => {
const withClient = fakeFetch();
const without = fakeFetch();

await makeApi(withClient, { client: "node-red-contrib-teslemetry" })._request("GET", "api/1/products");
await makeApi(without)._request("GET", "api/1/products");

expect(headersOf(withClient).get("X-Client")).toBe("node-red-contrib-teslemetry");
expect(headersOf(withClient).get("X-Library")).toMatch(/^node tesla_fleet_api /);
expect(headersOf(without).has("X-Client")).toBe(false);
});

it("rejects a client that is not printable ASCII", () => {
expect(() => makeApi(fakeFetch(), { client: "café" })).toThrow(/printable ASCII/);
expect(() => makeApi(fakeFetch(), { client: "app\n" })).toThrow(/printable ASCII/);
});

it("raises ResponseError for a non-JSON 2xx", async () => {
const fetch = fakeFetch(() => new Response("<html>ok</html>", { status: 200, headers: { "Content-Type": "text/html" } }));

Expand Down
Loading