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
47 changes: 47 additions & 0 deletions packages/shared/src/remote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,53 @@ describe("remote", () => {
});
});

it("rejects unsupported direct pairing URL protocols", () => {
let pairingUrlError: unknown;
try {
resolveRemotePairingTarget({
pairingUrl: "ftp://remote.example.com/pair#token=pairing-token",
});
} catch (cause) {
pairingUrlError = cause;
}

expect(pairingUrlError).toBeInstanceOf(RemotePairingUrlInvalidError);
expect(pairingUrlError).toMatchObject({ protocol: "ftp:" });
expect((pairingUrlError as RemotePairingUrlInvalidError).cause).toBeUndefined();
});

it("rejects unsupported hosted pairing backend protocols", () => {
let hostError: unknown;
try {
resolveRemotePairingTarget({
pairingUrl:
"https://app.t3.codes/pair?host=ftp%3A%2F%2Fremote.example.com#token=pairing-token",
});
} catch (cause) {
hostError = cause;
}

expect(hostError).toBeInstanceOf(RemoteBackendUrlInvalidError);
expect(hostError).toMatchObject({ source: "hosted-pairing-host", protocol: "ftp:" });
expect((hostError as RemoteBackendUrlInvalidError).cause).toBeUndefined();
});

it("rejects unsupported direct host protocols", () => {
let hostError: unknown;
try {
resolveRemotePairingTarget({
host: "ftp://remote.example.com",
pairingCode: "pairing-token",
});
} catch (cause) {
hostError = cause;
}

expect(hostError).toBeInstanceOf(RemoteBackendUrlInvalidError);
expect(hostError).toMatchObject({ source: "direct-host", protocol: "ftp:" });
expect((hostError as RemoteBackendUrlInvalidError).cause).toBeUndefined();
});

it("uses distinct structural errors for missing pairing inputs", () => {
expect(() => resolveRemotePairingTarget({})).toThrowError(RemoteBackendUrlMissingError);
expect(() =>
Expand Down
23 changes: 21 additions & 2 deletions packages/shared/src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Schema from "effect/Schema";
const PAIRING_TOKEN_PARAM = "token";
const HOSTED_PAIRING_HOST_PARAM = "host";
const HOSTED_PAIRING_LABEL_PARAM = "label";
const SUPPORTED_REMOTE_BACKEND_PROTOCOLS = new Set(["http:", "https:", "ws:", "wss:"]);

const readHashParams = (url: URL): URLSearchParams =>
new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);
Expand All @@ -18,7 +19,10 @@ export class RemoteBackendUrlMissingError extends Schema.TaggedErrorClass<Remote

export class RemotePairingUrlInvalidError extends Schema.TaggedErrorClass<RemotePairingUrlInvalidError>()(
"RemotePairingUrlInvalidError",
{ cause: Schema.Defect() },
{
cause: Schema.optional(Schema.Defect()),
protocol: Schema.optional(Schema.String),
},
) {
override get message(): string {
return "Pairing URL is invalid.";
Expand All @@ -29,7 +33,8 @@ export class RemoteBackendUrlInvalidError extends Schema.TaggedErrorClass<Remote
"RemoteBackendUrlInvalidError",
{
source: Schema.Literals(["direct-host", "hosted-pairing-host"]),
cause: Schema.Defect(),
cause: Schema.optional(Schema.Defect()),
protocol: Schema.optional(Schema.String),
},
) {
override get message(): string {
Expand Down Expand Up @@ -64,6 +69,9 @@ export const RemotePairingTargetError = Schema.Union([
]);
export type RemotePairingTargetError = typeof RemotePairingTargetError.Type;

const hasSupportedRemoteBackendProtocol = (url: URL): boolean =>
SUPPORTED_REMOTE_BACKEND_PROTOCOLS.has(url.protocol);

const normalizeRemoteBaseUrl = (
rawValue: string,
source: RemoteBackendUrlInvalidError["source"],
Expand All @@ -83,6 +91,12 @@ const normalizeRemoteBaseUrl = (
} catch (cause) {
throw new RemoteBackendUrlInvalidError({ source, cause });
}
if (!hasSupportedRemoteBackendProtocol(url)) {
throw new RemoteBackendUrlInvalidError({
source,
protocol: url.protocol,
});
}
url.pathname = "/";
url.search = "";
url.hash = "";
Expand Down Expand Up @@ -184,6 +198,11 @@ export const resolveRemotePairingTarget = (input: {
} catch (cause) {
throw new RemotePairingUrlInvalidError({ cause });
}
if (!hasSupportedRemoteBackendProtocol(url)) {
throw new RemotePairingUrlInvalidError({
protocol: url.protocol,
});
}
const hostedPairingRequest = readHostedPairingRequest(url);
if (hostedPairingRequest) {
const hostedBackendUrl = normalizeRemoteBaseUrl(
Expand Down
Loading