refactor: improve drag interaction and workspace identity normalization

This commit is contained in:
Mohamed Boudra
2026-03-04 12:25:14 +07:00
parent b5b4e60916
commit 4f1783fc76
12 changed files with 750 additions and 147 deletions
@@ -56,6 +56,12 @@ describe("workspace route parsing", () => {
expect(decodeWorkspaceIdFromPathSegment("L3RtcC9yZXBv")).toBe("/tmp/repo");
});
it("decodes non-canonical base64url workspace IDs used by older links", () => {
expect(
decodeWorkspaceIdFromPathSegment("L1VzZXJzL21vYm91ZHJhL2Rldi9wYXNlby")
).toBe("/Users/moboudra/dev/paseo");
});
it("encodes file paths as base64url (no padding)", () => {
const encoded = encodeFilePathForPathSegment("src/index.ts");
expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/);
+26 -1
View File
@@ -30,7 +30,7 @@ function toBase64UrlNoPad(input: string): string {
.replace(/=+$/g, "");
}
function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
function decodeBase64UrlNoPadUtf8(input: string): string | null {
const normalized = input.trim();
if (normalized.length === 0) {
return null;
@@ -49,6 +49,16 @@ function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
return null;
}
return decoded;
}
function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
const normalized = input.trim();
const decoded = decodeBase64UrlNoPadUtf8(normalized);
if (!decoded) {
return null;
}
// Validate via round-trip to avoid false positives ("workspace-1" etc).
if (toBase64UrlNoPad(decoded) !== normalized) {
return null;
@@ -57,6 +67,14 @@ function tryDecodeBase64UrlNoPadUtf8(input: string): string | null {
return decoded;
}
function isPathLikeWorkspaceIdentity(value: string): boolean {
return (
value.includes("/") ||
value.includes("\\") ||
/^[A-Za-z]:[\\/]/.test(value)
);
}
function normalizeWorkspaceId(value: string): string {
return value.trim().replace(/\\/g, "/").replace(/\/+$/, "");
}
@@ -106,6 +124,13 @@ export function decodeWorkspaceIdFromPathSegment(workspaceIdSegment: string): st
return normalizeWorkspaceId(base64Decoded);
}
// Some older links use non-canonical base64url (non-zero pad bits). Accept
// decoded values only when they clearly represent filesystem paths.
const relaxedBase64Decoded = decodeBase64UrlNoPadUtf8(decoded);
if (relaxedBase64Decoded && isPathLikeWorkspaceIdentity(relaxedBase64Decoded)) {
return normalizeWorkspaceId(relaxedBase64Decoded);
}
return normalizeWorkspaceId(decoded);
}
@@ -0,0 +1,20 @@
function trimNonEmpty(value: string | null | undefined): string | null {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
export function normalizeWorkspaceIdentity(value: string | null | undefined): string | null {
const trimmed = trimNonEmpty(value);
if (!trimmed) {
return null;
}
const withUnixSeparators = trimmed.replace(/\\/g, "/");
if (withUnixSeparators === "/") {
return withUnixSeparators;
}
const withoutTrailingSlash = withUnixSeparators.replace(/\/+$/, "");
return withoutTrailingSlash.length > 0 ? withoutTrailingSlash : "/";
}