diff --git a/.changeset/eight-eagles-joke.md b/.changeset/eight-eagles-joke.md new file mode 100644 index 000000000..23c685876 --- /dev/null +++ b/.changeset/eight-eagles-joke.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/cli": patch +"@trigger.dev/yalt": patch +--- + +updated the dev command to include -https flag diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index b55243de4..0a7ca9eaa 100644 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -63,6 +63,7 @@ program "-t, --tunnel ", "An optional custom tunnel URL. Use only if you already have an open tunnel to your local dev server." ) + .option("-s, --https", "allows enabled https for the tunnel") .version(getVersion(), "-v, --version", "Display the version number") .action(async (path, options) => { try { diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 8d6ba6cca..05bfc715c 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -6,11 +6,12 @@ import ora, { Ora } from "ora"; import pRetry, { AbortError } from "p-retry"; import util from "util"; import { z } from "zod"; +import https from "https"; import { Framework } from "../frameworks"; import { standardWatchFilePaths, standardWatchIgnoreRegex } from "../frameworks/watchConfig"; import { telemetryClient } from "../telemetry/telemetry"; import { getEnvFilename } from "../utils/env"; -import fetch from "../utils/fetchUseProxy"; +import fetch, {RequestInit} from "../utils/fetchUseProxy"; import { getTriggerApiDetails } from "../utils/getTriggerApiDetails"; import { JsRuntime, getJsRuntime } from "../utils/jsRuntime"; import { logger } from "../utils/logger"; @@ -35,6 +36,7 @@ export const DevCommandOptionsSchema = z.object({ .url() .regex(/^(http|https).+/, "only http/https URLs are accepted") .optional(), + https: z.boolean().default(false).optional(), }); export type DevCommandOptions = z.infer; @@ -59,6 +61,7 @@ type ResolvedUrl = { type: "resolved"; hostname: string; port: number; + https: boolean; }; type ServerUrl = TunnelUrl | ResolvedUrl; @@ -349,6 +352,7 @@ async function resolveOptions( handlerPath: unresolvedOptions.handlerPath, clientId: unresolvedOptions.clientId, tunnel: unresolvedOptions.tunnel, + https: unresolvedOptions.https, }; } @@ -362,6 +366,7 @@ async function resolveOptions( handlerPath: unresolvedOptions.handlerPath, clientId: unresolvedOptions.clientId, tunnel: unresolvedOptions.tunnel, + https: unresolvedOptions.https, }; } @@ -375,10 +380,11 @@ async function verifyEndpoint( //try each url for (const serverUrl of serverUrls) { + const protocol = resolvedOptions.https ? "https" : "http"; const url = serverUrl.type === "tunnel" ? serverUrl.url - : `http://${serverUrl.hostname}:${serverUrl.port}`; + : `${protocol}://${serverUrl.hostname}:${serverUrl.port}`; const localEndpointHandlerUrl = `${url}${resolvedOptions.handlerPath}`; const spinner = ora( @@ -386,14 +392,22 @@ async function verifyEndpoint( ).start(); try { - const response = await fetch(localEndpointHandlerUrl, { + const agent = new https.Agent({ + rejectUnauthorized: false, // Ignore self-signed certificates + }); + + // Conditionally include the agent in fetch options + const fetchOptions: RequestInit = { method: "POST", headers: { "x-trigger-api-key": apiKey, "x-trigger-action": "PING", "x-trigger-endpoint-id": endpointId, }, - }); + ...(resolvedOptions.https && { agent }), + }; + + const response = await fetch(localEndpointHandlerUrl, fetchOptions); if (!response.ok || response.status !== 200) { spinner.fail( @@ -404,7 +418,11 @@ async function verifyEndpoint( spinner.succeed(`[trigger.dev] Found your trigger endpoint: ${localEndpointHandlerUrl}`); - return { ...serverUrl, handlerPath: resolvedOptions.handlerPath }; + return { + ...serverUrl, + handlerPath: resolvedOptions.handlerPath, + https: resolvedOptions.https ?? false, + }; } catch (err) { spinner.fail(`[trigger.dev] No server found (${localEndpointHandlerUrl}).`); } @@ -451,7 +469,7 @@ function findServerUrls(resolvedOptions: ResolvedOptions, framework?: Framework) const urls: ResolvedUrl[] = []; for (const hostname of hostnames) { for (const port of ports) { - urls.push({ type: "resolved", hostname, port }); + urls.push({ type: "resolved", hostname, port, https: resolvedOptions.https ?? false }); } } @@ -480,6 +498,7 @@ async function resolveEndpointUrl(apiUrl: string, apiKey: string, endpoint: Serv const tunnelUrl = await createNativeTunnel( endpoint.hostname, endpoint.port, + endpoint.https, triggerApi, tunnelSpinner ); @@ -507,6 +526,7 @@ let yaltTunnel: YaltTunnel | null = null; async function createNativeTunnel( hostname: string, port: number, + https: boolean, triggerApi: TriggerApi, spinner: Ora ) { @@ -519,6 +539,7 @@ async function createNativeTunnel( yaltTunnel = new YaltTunnel( response.url, `${hostname}:${port}`, + https, { WebSocket: WebSocket.default, connectionTimeout: 1000, diff --git a/packages/yalt/package.json b/packages/yalt/package.json index 071e5a156..0e2836404 100644 --- a/packages/yalt/package.json +++ b/packages/yalt/package.json @@ -31,7 +31,10 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "https": "^1.0.0", + "node-fetch": "^3.3.2", "partysocket": "^0.0.17", + "proxy-agent": "^6.3.0", "zod": "3.22.3" }, "devDependencies": { diff --git a/packages/yalt/src/index.ts b/packages/yalt/src/index.ts index cb7003e52..aeea35e39 100644 --- a/packages/yalt/src/index.ts +++ b/packages/yalt/src/index.ts @@ -1,5 +1,12 @@ import { z } from "zod"; import { WebSocket } from "partysocket"; +import node_fetch, { + RequestInfo as _RequestInfo, + RequestInit as _RequestInit, + Response, +} from "node-fetch"; +import { ProxyAgent } from "proxy-agent"; +import https from "https"; export const RequestMesssage = z.object({ type: z.literal("request"), @@ -8,6 +15,7 @@ export const RequestMesssage = z.object({ method: z.string(), url: z.string(), body: z.string(), + https: z.boolean().default(false).optional(), }); export type RequestMessage = z.infer; @@ -28,6 +36,9 @@ export const ServerMessages = z.discriminatedUnion("type", [RequestMesssage]); export type ClientMessage = z.infer; export type ServerMessage = z.infer; +export type RequestInfo = _RequestInfo; +export type RequestInit = _RequestInit; + export async function createRequestMessage(id: string, request: Request): Promise { const { headers, method, url } = request; @@ -76,7 +87,7 @@ export class YaltApiClient { throw new Error(`Could not create tunnel: ${response.status}`); } - const body = await response.json(); + const body = (await response.json()) as any; return body.id; } @@ -102,6 +113,7 @@ export class YaltTunnel { constructor( private url: string, private address: string, + private https: boolean, private socketOptions: YaltTunnelSocketOptions = {}, private options: YaltTunnelOptions = {} ) {} @@ -165,7 +177,9 @@ export class YaltTunnel { const url = new URL(request.url); // Construct the original url to be the same as the request URL but with a different hostname and using http instead of https - const originalUrl = new URL(`http://${this.address}${url.pathname}${url.search}${url.hash}`); + const originalUrl = new URL( + `${this.https ? "https" : "http"}://${this.address}${url.pathname}${url.search}${url.hash}` + ); let response: Response | null = null; @@ -176,10 +190,14 @@ export class YaltTunnel { }); try { + const agent = new https.Agent({ + rejectUnauthorized: false, // Ignore self-signed certificates + }); response = await fetch(originalUrl.href, { method: request.method, headers: stripHeaders(request.headers), body: request.body, + ...(this.https && { agent }), }); } catch (error) { if (error instanceof Error) { @@ -234,3 +252,14 @@ function stripHeaders(headers: Record) { Object.entries(headers).filter(([key]) => !blacklistHeaders.includes(key.toLowerCase())) ); } + +function fetch(url: RequestInfo, init?: RequestInit) { + const fetchInit: RequestInit = { ...init }; + + // If agent is not specified, specify proxy-agent and use environment variables such as HTTPS_PROXY. + if (!fetchInit.agent) { + fetchInit.agent = new ProxyAgent(); + } + + return node_fetch(url, fetchInit); +}