fix alpaca news auth and native worker wiring

This commit is contained in:
dirtydishes 2026-05-19 19:57:56 -04:00
parent e9739f5dc9
commit 7d25608b35
21 changed files with 285 additions and 80 deletions

View file

@ -0,0 +1,76 @@
export type AlpacaCredentials = {
keyId: string;
secret: string;
legacyToken: string;
usesLegacyBearer: boolean;
};
type AlpacaCredentialEnv = {
ALPACA_API_KEY?: string;
ALPACA_API_KEY_ID?: string;
ALPACA_KEY_ID?: string;
ALPACA_API_SECRET_KEY?: string;
ALPACA_SECRET_KEY?: string;
};
const normalize = (value: string | undefined): string => value?.trim() ?? "";
export const resolveAlpacaCredentials = (
env: AlpacaCredentialEnv
): AlpacaCredentials => {
const legacyToken = normalize(env.ALPACA_API_KEY);
const explicitKeyId =
normalize(env.ALPACA_API_KEY_ID) || normalize(env.ALPACA_KEY_ID);
const secret =
normalize(env.ALPACA_API_SECRET_KEY) || normalize(env.ALPACA_SECRET_KEY);
const keyId = explicitKeyId || legacyToken;
const usesLegacyBearer = !explicitKeyId && !secret && legacyToken.length > 0;
return {
keyId,
secret,
legacyToken,
usesLegacyBearer
};
};
export const hasAlpacaCredentials = (credentials: AlpacaCredentials): boolean => {
if (credentials.usesLegacyBearer) {
return credentials.legacyToken.length > 0;
}
return credentials.keyId.length > 0 && credentials.secret.length > 0;
};
export const buildAlpacaAuthHeaders = (
credentials: AlpacaCredentials
): Record<string, string> => {
if (credentials.usesLegacyBearer) {
return {
Authorization: `Bearer ${credentials.legacyToken}`
};
}
return {
"APCA-API-KEY-ID": credentials.keyId,
"APCA-API-SECRET-KEY": credentials.secret
};
};
export const buildAlpacaWebSocketAuthMessage = (
credentials: AlpacaCredentials
): { action: "auth"; key: string; secret: string } => {
if (credentials.usesLegacyBearer) {
return {
action: "auth",
key: credentials.legacyToken,
secret: ""
};
}
return {
action: "auth",
key: credentials.keyId,
secret: credentials.secret
};
};

View file

@ -1 +1,2 @@
export * from "./env";
export * from "./alpaca";

View file

@ -0,0 +1,65 @@
import { describe, expect, it } from "bun:test";
import {
buildAlpacaAuthHeaders,
buildAlpacaWebSocketAuthMessage,
hasAlpacaCredentials,
resolveAlpacaCredentials
} from "../src/alpaca";
describe("resolveAlpacaCredentials", () => {
it("prefers explicit key-id and secret vars", () => {
const credentials = resolveAlpacaCredentials({
ALPACA_API_KEY: "legacy-token",
ALPACA_API_KEY_ID: "key-id",
ALPACA_API_SECRET_KEY: "secret"
});
expect(credentials).toEqual({
keyId: "key-id",
secret: "secret",
legacyToken: "legacy-token",
usesLegacyBearer: false
});
expect(hasAlpacaCredentials(credentials)).toBe(true);
expect(buildAlpacaAuthHeaders(credentials)).toEqual({
"APCA-API-KEY-ID": "key-id",
"APCA-API-SECRET-KEY": "secret"
});
expect(buildAlpacaWebSocketAuthMessage(credentials)).toEqual({
action: "auth",
key: "key-id",
secret: "secret"
});
});
it("supports the older bearer-token fallback when no secret exists", () => {
const credentials = resolveAlpacaCredentials({
ALPACA_API_KEY: "legacy-token"
});
expect(credentials.usesLegacyBearer).toBe(true);
expect(hasAlpacaCredentials(credentials)).toBe(true);
expect(buildAlpacaAuthHeaders(credentials)).toEqual({
Authorization: "Bearer legacy-token"
});
expect(buildAlpacaWebSocketAuthMessage(credentials)).toEqual({
action: "auth",
key: "legacy-token",
secret: ""
});
});
it("supports alternate secret env names", () => {
const credentials = resolveAlpacaCredentials({
ALPACA_KEY_ID: "short-key",
ALPACA_SECRET_KEY: "short-secret"
});
expect(credentials).toEqual({
keyId: "short-key",
secret: "short-secret",
legacyToken: "",
usesLegacyBearer: false
});
});
});