diff --git a/.changeset/ready-horses-punch.md b/.changeset/ready-horses-punch.md new file mode 100644 index 0000000..7e880cf --- /dev/null +++ b/.changeset/ready-horses-punch.md @@ -0,0 +1,5 @@ +--- +"@scrawn/core": patch +--- + +feat: pass in httpurl and webhook public key to constructor diff --git a/examples/scrawn/biller.ts b/examples/scrawn/biller.ts index 19a8c6d..3784d4d 100644 --- a/examples/scrawn/biller.ts +++ b/examples/scrawn/biller.ts @@ -4,7 +4,9 @@ import { TAGS, EXPRESSIONS } from "./pricerefs.ts"; export const biller = scrawn({ apiKey: process.env.SCRAWN_KEY as string, baseURL: process.env.SCRAWN_BASE_URL as string, + httpUrl: process.env.SCRAWN_HTTP_URL as string, secure: process.env.SCRAWN_BASE_URL?.startsWith("https") ?? false, tags: TAGS, expressions: EXPRESSIONS, + webhookPublicKey: process.env.SCRAWN_WEBHOOK_PUBLIC_KEY, }); diff --git a/packages/scrawn/src/core/auth/apiKeyAuth.ts b/packages/scrawn/src/core/auth/apiKeyAuth.ts index f766d24..9e0697e 100644 --- a/packages/scrawn/src/core/auth/apiKeyAuth.ts +++ b/packages/scrawn/src/core/auth/apiKeyAuth.ts @@ -16,15 +16,11 @@ const API_KEY_REGEX = /^scrn_(dash|live|test)_[a-zA-Z0-9]{32}$/; /** * Type guard to validate API key format */ -function isValidApiKey(key: string): key is ApiKeyFormat { +export function isValidApiKey(key: string): key is ApiKeyFormat { return API_KEY_REGEX.test(key); } -/** - * Validates and returns a properly typed API key - * @throws Error if the API key format is invalid - */ -function validateApiKey(key: string): ApiKeyFormat { +export function validateApiKey(key: string): ApiKeyFormat { if (!isValidApiKey(key)) { log.error(`Invalid API key format: "${key}".`); throw new ScrawnValidationError( diff --git a/packages/scrawn/src/core/scrawn.ts b/packages/scrawn/src/core/scrawn.ts index 687d2be..d1ca92c 100644 --- a/packages/scrawn/src/core/scrawn.ts +++ b/packages/scrawn/src/core/scrawn.ts @@ -114,6 +114,7 @@ const log = new ScrawnLogger("Scrawn"); * const biller = scrawn({ * apiKey: process.env.SCRAWN_KEY, * baseURL: 'http://localhost:8069', + * httpUrl: 'http://localhost:8070', * tags: ["PREMIUM_CALL", "EXTRA_FEE"] as const, * }); * @@ -205,13 +206,15 @@ export class Scrawn< * * @param config - Configuration object * @param config.apiKey - Your Scrawn API key for authentication - * @param config.baseURL - Base URL for the Scrawn API (e.g., 'https://api.scrawn.dev') + * @param config.baseURL - Base URL for the Scrawn gRPC API (e.g., 'http://localhost:8069') + * @param config.httpUrl - HTTP URL for the Scrawn HTTP API (e.g., 'http://localhost:8070') * * @example * ```typescript * const scrawn = new Scrawn({ * apiKey: 'sk_test_...', - * baseURL: 'https://api.scrawn.dev' + * baseURL: 'http://localhost:8069', + * httpUrl: 'http://localhost:8070', * }); * await scrawn.init(); * ``` @@ -219,9 +222,11 @@ export class Scrawn< constructor(config: { apiKey: AllCredentials["apiKey"]; baseURL: string; + httpUrl: string; secure?: boolean; credentials?: import("@grpc/grpc-js").ChannelCredentials; retryCount?: number; + webhookPublicKey?: string; }) { try { // Validate configuration @@ -243,9 +248,21 @@ export class Scrawn< ); } + if (!config.httpUrl || typeof config.httpUrl !== "string") { + throw new ScrawnConfigError( + "httpUrl is required and must be a string", + { + details: { provided: typeof config.httpUrl }, + } + ); + } + this.apiKey = config.apiKey; this.retryCount = config.retryCount ?? 2; - this.httpUrl = this.buildHttpUrl(config.baseURL); + this.httpUrl = config.httpUrl; + if (config.webhookPublicKey) { + this.cachedPublicKey = config.webhookPublicKey; + } this.grpcClient = new GrpcClient(this.parseURLToTarget(config.baseURL), { secure: config.secure ?? true, credentials: config.credentials, @@ -268,16 +285,6 @@ export class Scrawn< : `${baseURL}:${ScrawnConfig.grpc.defaultPort}`; } - private buildHttpUrl(baseURL: string): string { - if (baseURL.includes("://")) { - const url = new URL(baseURL); - return `http://${url.hostname}:8070`; - } - - const host = baseURL.includes(":") ? baseURL.split(":")[0] : baseURL; - return `http://${host}:8070`; - } - /** * Create a type-safe tag reference. * @@ -1307,6 +1314,7 @@ export class Scrawn< export interface ScrawnInitConfig { apiKey: string; baseURL: string; + httpUrl: string; secure?: boolean; credentials?: import("@grpc/grpc-js").ChannelCredentials; tags?: readonly string[]; @@ -1317,6 +1325,12 @@ export interface ScrawnInitConfig { * Each event also gets a manual `.retry()` context in the onError callback. */ retryCount?: number; + /** + * Optional webhook public key to skip fetching it from the backend. + * The dashboard displays this key — paste it here to avoid an extra HTTP + * call on every cold start of biller.webhook(). + */ + webhookPublicKey?: string; } /** @@ -1333,6 +1347,7 @@ export interface ScrawnInitConfig { * const biller = scrawn({ * apiKey: process.env.SCRAWN_KEY, * baseURL: process.env.SCRAWN_BASE_URL, + * httpUrl: process.env.SCRAWN_HTTP_URL, * tags: ["PREMIUM_CALL", "EXTRA_FEE"] as const, * expressions: ["MY_EXPR"] as const, * }); @@ -1363,8 +1378,10 @@ export function scrawn( return new Scrawn({ apiKey: config.apiKey as AllCredentials["apiKey"], baseURL: config.baseURL, + httpUrl: config.httpUrl, secure: config.secure, credentials: config.credentials, retryCount: config.retryCount, + webhookPublicKey: config.webhookPublicKey, }); } diff --git a/packages/scrawn/tests/unit/scrawn/middleware.test.ts b/packages/scrawn/tests/unit/scrawn/middleware.test.ts index 6207201..420132e 100644 --- a/packages/scrawn/tests/unit/scrawn/middleware.test.ts +++ b/packages/scrawn/tests/unit/scrawn/middleware.test.ts @@ -54,6 +54,7 @@ describe("middlewareEventConsumer", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); const middleware = biller.middlewareEventConsumer({ @@ -73,6 +74,7 @@ describe("middlewareEventConsumer", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); const middleware = biller.middlewareEventConsumer({ @@ -92,6 +94,7 @@ describe("middlewareEventConsumer", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); const middleware = biller.middlewareEventConsumer({ @@ -110,6 +113,7 @@ describe("middlewareEventConsumer", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", retryCount: 0, }); attachMockClient(biller); @@ -135,6 +139,7 @@ describe("middlewareEventConsumer", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); const onError = vi.fn(); diff --git a/packages/scrawn/tests/unit/scrawn/scrawn.test.ts b/packages/scrawn/tests/unit/scrawn/scrawn.test.ts index 690703d..b141966 100644 --- a/packages/scrawn/tests/unit/scrawn/scrawn.test.ts +++ b/packages/scrawn/tests/unit/scrawn/scrawn.test.ts @@ -59,6 +59,9 @@ describe("Scrawn", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); @@ -77,6 +80,8 @@ describe("Scrawn", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); @@ -93,6 +98,8 @@ describe("Scrawn", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); const link = await biller.collectPayment("user_1"); @@ -112,6 +119,8 @@ describe("Scrawn", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", }); attachMockClient(biller); @@ -124,6 +133,8 @@ describe("Scrawn", () => { const biller = scrawn({ apiKey: validKey, baseURL: "https://api.example", + httpUrl: "https://api.example", + httpUrl: "https://api.example", retryCount: 0, }); const onError = vi.fn();