Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import apiClient from "@app/services/apiClient";
import {
parseContentDispositionFilename,
extractLatestFilesFromBundle,
readResponseHeader,
} from "@app/services/shareBundleUtils";
import { truncateCenter } from "@app/utils/textUtils";
import { generateThumbnailForFile } from "@app/utils/thumbnailUtils";
Expand Down Expand Up @@ -269,14 +270,8 @@ export function FileSelectorPicker({
skipAuthRedirect: true,
} as any,
);
const ct =
res.headers?.["content-type"] ||
res.headers?.["Content-Type"] ||
"";
const disp =
res.headers?.["content-disposition"] ||
res.headers?.["Content-Disposition"] ||
"";
const ct = readResponseHeader(res.headers, "content-type");
const disp = readResponseHeader(res.headers, "content-disposition");
const files = await extractLatestFilesFromBundle(
res.data as Blob,
parseContentDispositionFilename(disp) || "shared-file",
Expand All @@ -293,14 +288,8 @@ export function FileSelectorPicker({
skipAuthRedirect: true,
} as any,
);
const ct =
res.headers?.["content-type"] ||
res.headers?.["Content-Type"] ||
"";
const disp =
res.headers?.["content-disposition"] ||
res.headers?.["Content-Disposition"] ||
"";
const ct = readResponseHeader(res.headers, "content-type");
const disp = readResponseHeader(res.headers, "content-disposition");
const files = await extractLatestFilesFromBundle(
res.data as Blob,
parseContentDispositionFilename(disp) || stub.name,
Expand Down
22 changes: 9 additions & 13 deletions frontend/editor/src/core/contexts/FileManagerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { alert } from "@app/components/toast";
import {
extractLatestFilesFromBundle,
parseContentDispositionFilename,
readResponseHeader,
} from "@app/services/shareBundleUtils";
import { useTranslation } from "react-i18next";
import { openFilesFromDisk } from "@app/services/openFilesFromDisk";
Expand Down Expand Up @@ -1002,16 +1003,14 @@ export const FileManagerProvider: React.FC<FileManagerProviderProps> = ({
skipAuthRedirect: true,
} as any,
);
const contentType =
(response.headers &&
(response.headers["content-type"] ||
response.headers["Content-Type"])) ||
"";
const disposition =
(response.headers &&
(response.headers["content-disposition"] ||
response.headers["Content-Disposition"])) ||
"";
const contentType = readResponseHeader(
response.headers,
"content-type",
);
const disposition = readResponseHeader(
response.headers,
"content-disposition",
);
const filename =
parseContentDispositionFilename(disposition) ||
file.name ||
Expand Down Expand Up @@ -1269,6 +1268,3 @@ export const useFileManagerContext = (): FileManagerContextValue => {

return context;
};

// Export the context for advanced use cases
export { FileManagerContext };
31 changes: 11 additions & 20 deletions frontend/editor/src/core/contexts/FilesModalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
isZipBundle,
loadShareBundleEntries,
parseContentDispositionFilename,
readResponseHeader,
} from "@app/services/shareBundleUtils";

interface FilesModalContextType {
Expand Down Expand Up @@ -184,16 +185,11 @@ export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({
skipAuthRedirect: true,
} as any,
);
const contentType =
(response.headers &&
(response.headers["content-type"] ||
response.headers["Content-Type"])) ||
"";
const disposition =
(response.headers &&
(response.headers["content-disposition"] ||
response.headers["Content-Disposition"])) ||
"";
const contentType = readResponseHeader(response.headers, "content-type");
const disposition = readResponseHeader(
response.headers,
"content-disposition",
);
const filename =
parseContentDispositionFilename(disposition) || "server-file";
const blob = response.data as Blob;
Expand All @@ -210,16 +206,11 @@ export const FilesModalProvider: React.FC<{ children: React.ReactNode }> = ({
skipAuthRedirect: true,
} as any,
);
const contentType =
(response.headers &&
(response.headers["content-type"] ||
response.headers["Content-Type"])) ||
"";
const disposition =
(response.headers &&
(response.headers["content-disposition"] ||
response.headers["Content-Disposition"])) ||
"";
const contentType = readResponseHeader(response.headers, "content-type");
const disposition = readResponseHeader(
response.headers,
"content-disposition",
);
const filename =
parseContentDispositionFilename(disposition) || "shared-file";
const blob = response.data as Blob;
Expand Down
42 changes: 42 additions & 0 deletions frontend/editor/src/core/services/shareBundleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,48 @@ import type { ShareBundleManifest } from "@app/services/serverStorageBundle";

const MANIFEST_FILENAME = "stirling-share.json";

type HeaderLike = {
get?: (name: string) => unknown;
[key: string]: unknown;
};

function headerValueToString(value: unknown): string {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
if (Array.isArray(value)) {
const firstString = value.find(
(entry): entry is string => typeof entry === "string",
);
return firstString ?? "";
}
return "";
}

function toHeaderKey(name: string): string {
return name.replace(
/(^|-)([a-z])/g,
(_, prefix: string, char: string) => prefix + char.toUpperCase(),
);
}

export function readResponseHeader(headers: unknown, name: string): string {
if (!headers || typeof headers !== "object") {
return "";
}

const typedHeaders = headers as HeaderLike;
if (typeof typedHeaders.get === "function") {
return headerValueToString(typedHeaders.get(name));
}

return (
headerValueToString(typedHeaders[name]) ||
headerValueToString(typedHeaders[toHeaderKey(name)])
);
}

export function parseContentDispositionFilename(
disposition?: string,
): string | null {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import apiClient from "@app/services/apiClient";
import type { SavedSignature } from "@app/types/signature";
import { readResponseHeader } from "@app/services/shareBundleUtils";

export type StorageType = "backend" | "localStorage";

Expand Down Expand Up @@ -166,7 +167,9 @@ class SignatureStorageService {

// Convert to data URL (base64) for both display and use
const blob = new Blob([imageResponse.data], {
type: imageResponse.headers["content-type"] || "image/png",
type:
readResponseHeader(imageResponse.headers, "content-type") ||
"image/png",
});

const dataUrl = await new Promise<string>((resolve, reject) => {
Expand Down
19 changes: 9 additions & 10 deletions frontend/editor/src/proprietary/routes/ShareLinkLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isZipBundle,
loadShareBundleEntries,
parseContentDispositionFilename,
readResponseHeader,
} from "@app/services/shareBundleUtils";

interface ShareLinkLoaderProps {
Expand Down Expand Up @@ -78,16 +79,14 @@ export default function ShareLinkLoader({ token }: ShareLinkLoaderProps) {
);
if (signal.aborted) return;

const contentType =
(response.headers &&
(response.headers["content-type"] ||
response.headers["Content-Type"])) ||
"";
const disposition =
(response.headers &&
(response.headers["content-disposition"] ||
response.headers["Content-Disposition"])) ||
"";
const contentType = readResponseHeader(
response.headers,
"content-type",
);
const disposition = readResponseHeader(
response.headers,
"content-disposition",
);
const filename =
parseContentDispositionFilename(disposition) || "shared-file";
const blob = response.data as Blob;
Expand Down
15 changes: 6 additions & 9 deletions frontend/editor/src/proprietary/services/shareLinkImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isZipBundle,
loadShareBundleEntries,
parseContentDispositionFilename,
readResponseHeader,
} from "@app/services/shareBundleUtils";

export interface ShareLinkMetadata {
Expand Down Expand Up @@ -44,15 +45,11 @@ export async function downloadShareLink(token: string): Promise<{
suppressErrorToast: true,
skipAuthRedirect: true,
});
const contentType =
(response.headers &&
(response.headers["content-type"] || response.headers["Content-Type"])) ||
"";
const disposition =
(response.headers &&
(response.headers["content-disposition"] ||
response.headers["Content-Disposition"])) ||
"";
const contentType = readResponseHeader(response.headers, "content-type");
const disposition = readResponseHeader(
response.headers,
"content-disposition",
);
const filename =
parseContentDispositionFilename(disposition) || "shared-file";
const blob = response.data as Blob;
Expand Down
Loading
Loading