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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ A breaking change will get clearly marked in this log.

## Unreleased

### Fixed

* X-App-Name and X-App-Version headers are now included when using `CallBuilder.stream()` ([#1317](https://github.com/stellar/js-stellar-sdk/pull/1317)).


## [v14.4.3](https://github.com/stellar/js-stellar-sdk/compare/v14.4.2...v14.4.3)

Expand Down
21 changes: 21 additions & 0 deletions src/horizon/call_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ export class CallBuilder<
this.url.setQuery("X-Client-Name", "js-stellar-sdk");
this.url.setQuery("X-Client-Version", version);

// Extract custom app headers from httpClient defaults and add as query params
// (EventSource doesn't support custom headers, so we use query params)
const { headers } = this.httpClient.defaults;
if (headers) {
const headerNames = ["X-App-Name", "X-App-Version"];
headerNames.forEach((name) => {
let value: string | undefined;
if (headers instanceof Headers) {
value = headers.get(name) ?? undefined;
Comment thread
Shaptic marked this conversation as resolved.
} else if (Array.isArray(headers)) {
const entry = headers.find(([key]) => key === name);
value = entry?.[1];
} else {
value = headers[name];
}
if (value) {
this.url.setQuery(name, value);
}
});
}

// EventSource object
let es: EventSource;
// timeout is the id of the timeout to be triggered if there were no new messages
Expand Down
82 changes: 82 additions & 0 deletions test/integration/client_headers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,86 @@ describe("integration tests: client headers", () => {
.operations()
.call();
});

it("sends appName and appVersion via headers for HTTP requests", async () => {
let server: http.Server;

const requestHandler = (
request: http.IncomingMessage,
response: http.ServerResponse,
) => {
expect(request.headers["x-app-name"]).toBe("my-app");
expect(request.headers["x-app-version"]).toBe("1.0.0");
response.end();
server.close();
};

server = http.createServer(requestHandler);

await new Promise<void>((resolve, reject) => {
server.listen(port, (err?: Error) => {
if (err) {
reject(err);
return;
}
resolve();
});
});

await new Horizon.Server(`http://localhost:${port}`, {
appName: "my-app",
appVersion: "1.0.0",
allowHttp: true,
})
.operations()
.call();
});

it("sends appName and appVersion via query params when streaming", async () => {
let server: http.Server;
let closeStream: () => void;

const requestHandler = (
request: http.IncomingMessage,
response: http.ServerResponse,
) => {
const query = url.parse(request.url!, true).query;
expect(query["X-App-Name"]).toBe("my-app");
expect(query["X-App-Version"]).toBe("1.0.0");

response.writeHead(200, {
"Content-Type": "text/event-stream",
});
response.write("retry: 10\nevent: close\ndata: byebye\n\n");
response.end();

server.close(() => {
closeStream();
});
};

server = http.createServer(requestHandler);

await new Promise<void>((resolve, reject) => {
server.listen(port, (err?: Error) => {
if (err) {
reject(err);
return;
}
resolve();
});
});

closeStream = new Horizon.Server(`http://localhost:${port}`, {
appName: "my-app",
appVersion: "1.0.0",
allowHttp: true,
})
.operations()
.stream({
onerror: (err) => {
throw err;
},
});
});
});
3 changes: 2 additions & 1 deletion test/unit/call_builders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import { describe, it, expect } from "vitest";
import URI from "urijs";
import { CallBuilder } from "../../lib/horizon/call_builder";
import { httpClient } from "../test-utils/stellar-sdk-import";

describe("CallBuilder functions", () => {
it("doesn't mutate the constructor passed url argument (it clones it instead)", () => {
const arg = URI("https://onedom.ain/");
const builder = new CallBuilder(arg);
const builder = new CallBuilder(arg, httpClient);
builder["url"].segment("one_segment");
builder["checkFilter"]();

Expand Down
Loading