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
8 changes: 1 addition & 7 deletions src/pages/HistoryDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -456,13 +456,7 @@ export default function HistoryDetailsPage(props: {
<audio key={audio.id} controls controlsList="">
<source
src={audio.read_signed_url}
type={
"audio/" +
audio.read_signed_url
.split(".")
.pop()
?.split("?")[0]
}
type={audio.mime_type}
/>
Your browser does not support the audio element.
</audio>
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ export interface ScribeFileModel {
name: string;
upload_completed: boolean;
read_signed_url: string;
mime_type: string;
internal_name: string;
length: number;
}

Expand Down
85 changes: 43 additions & 42 deletions src/utils/upload-utils.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { ScribeFileType } from "@/types";
import { Dispatch, SetStateAction } from "react";
import { API } from "./api";

export function handleUploadPercentage(
event: ProgressEvent,
setUploadPercent: Dispatch<SetStateAction<number>>,
) {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
setUploadPercent(percentComplete);
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === "object" && !Array.isArray(value);

const safeParseJson = (value: string): unknown => {
try {
return JSON.parse(value);
} catch {
return value;
}
}
};

const uploadFile = (
url: string,
file: File | FormData,
reqMethod: string,
headers: object,
headers: Record<string, string>,
onLoad: (xhr: XMLHttpRequest) => void,
setUploadPercent: Dispatch<SetStateAction<number>> | null,
onError: () => void,
onError: (error?: string) => void,
) => {
const xhr = new XMLHttpRequest();
xhr.open(reqMethod, url);
xhr.open("PUT", url);

Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
Expand All @@ -31,23 +29,17 @@ const uploadFile = (
xhr.onload = () => {
onLoad(xhr);
if (400 <= xhr.status && xhr.status <= 499) {
const error = JSON.parse(xhr.responseText);
if (typeof error === "object" && !Array.isArray(error)) {
const error = safeParseJson(xhr.responseText ?? "");
if (isRecord(error)) {
Object.values(error).forEach((msg) => {
window.alert(msg || "Something went wrong!");
onError(typeof msg === "string" ? msg : "An error occurred.");
});
} else {
window.alert(error || "Something went wrong!");
return;
}
onError(typeof error === "string" ? error : "An error occurred");
}
Comment on lines 29 to 40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the upload-utils file
fd -t f upload-utils.ts

Repository: ohcnetwork/care_scribe_fe

Length of output: 94


🏁 Script executed:

# View the full file to understand the context
cat -n src/utils/upload-utils.ts

Repository: ohcnetwork/care_scribe_fe

Length of output: 4121


🏁 Script executed:

# Search for uploadFile function calls across the codebase
rg -nP --type=ts --type=tsx 'uploadFile\s*\(' -C 3

Repository: ohcnetwork/care_scribe_fe

Length of output: 96


🏁 Script executed:

# Search for uploadFile usage across the codebase
rg -n 'uploadFile' --type ts -C 3

Repository: ohcnetwork/care_scribe_fe

Length of output: 1056


🏁 Script executed:

# Also search in tsx files (without specifying type to avoid parsing issues)
rg -n 'uploadFile' -C 3 | head -100

Repository: ohcnetwork/care_scribe_fe

Length of output: 1056


🏁 Script executed:

# Search for any imports or usage of uploadFile in the entire codebase
rg -n 'from.*upload-utils' -C 2

Repository: ohcnetwork/care_scribe_fe

Length of output: 774


🏁 Script executed:

# Also check if uploadFile is used anywhere else beyond uploadScribeFile
rg -n 'uploadFile\(' -C 5

Repository: ohcnetwork/care_scribe_fe

Length of output: 681


Move error status checks before onLoad call to prevent double-callback invocation.

The xhr.onload handler currently calls onLoad(xhr) unconditionally (line 30), then checks for 4xx errors and invokes onError afterward. When a 4xx response occurs, both callbacks fire: the caller's onLoad handler (line 110) rejects the Promise on non-2xx status, then onError attempts to reject again. While a Promise only uses the first rejection, this pattern is semantically incorrect and confusing.

The fix should check error status codes first and only invoke onLoad on success (2xx). Additionally, handle 5xx server errors gracefully rather than waiting for network failure.

🔧 Proposed fix
-  xhr.onload = () => {
-    onLoad(xhr);
-    if (400 <= xhr.status && xhr.status <= 499) {
-      const error = safeParseJson(xhr.responseText ?? "");
-      if (isRecord(error)) {
-        Object.values(error).forEach((msg) => {
-          onError(typeof msg === "string" ? msg : "An error occurred.");
-        });
-        return;
-      }
-      onError(typeof error === "string" ? error : "An error occurred");
-    }
-  };
+  xhr.onload = () => {
+    if (400 <= xhr.status && xhr.status <= 499) {
+      const error = safeParseJson(xhr.responseText ?? "");
+      if (isRecord(error)) {
+        Object.values(error).forEach((msg) => {
+          onError(typeof msg === "string" ? msg : "An error occurred.");
+        });
+        return;
+      }
+      onError(typeof error === "string" ? error : "An error occurred");
+      return;
+    }
+    if (xhr.status >= 500) {
+      onError(`Upload failed (${xhr.status})`);
+      return;
+    }
+    onLoad(xhr);
+  };
🤖 Prompt for AI Agents
In `@src/utils/upload-utils.ts` around lines 29 - 40, Move the HTTP status checks
to run before calling the success callback: in the xhr.onload handler, first
inspect xhr.status and handle error ranges (400–499 and 500–599) by parsing
xhr.responseText with safeParseJson and invoking onError (using isRecord to
iterate messages or a fallback string) and return early; only call onLoad(xhr)
when status is a successful 2xx code. Ensure you do not call onError after
onLoad to avoid double-callbacks and handle 5xx server errors similarly to 4xx
by emitting a generic server error message if parsing fails.

};

if (setUploadPercent != null) {
xhr.upload.onprogress = (event: ProgressEvent) => {
handleUploadPercentage(event, setUploadPercent);
};
}

xhr.onerror = () => {
window.alert("Network Failure. Please check your internet connectivity.");
onError();
Expand All @@ -64,16 +56,27 @@ export const uploadScribeFile = async (
type: ScribeFileType,
) => {
const category = type === ScribeFileType.AUDIO ? "AUDIO" : "UNSPECIFIED";
const extension = blob?.type?.split("/")?.[1].split(";")?.[0];
const name = "file" + (extension ? `.${extension}` : "");
const extension = blob.type.split("/")[1]?.split(";")?.[0];

if (!extension) {
throw new Error(
"Could not determine file extension. Got mime type: " + blob.type,
);
}

const name = `file.${extension}`;
Comment on lines +59 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/utils/upload-utils.ts | head -100

Repository: ohcnetwork/care_scribe_fe

Length of output: 3488


🏁 Script executed:

rg -nP --type=ts -C3 'createFileUpload'

Repository: ohcnetwork/care_scribe_fe

Length of output: 902


🏁 Script executed:

rg -nP --type=ts 'CreateFileRequest' -A10 -B2

Repository: ohcnetwork/care_scribe_fe

Length of output: 1820


🏁 Script executed:

rg -nP --type=ts 'mime_type' src/

Repository: ohcnetwork/care_scribe_fe

Length of output: 300


Derive mimeType once to keep types strict and avoid optional chaining.

The mime_type field in CreateFileRequest is required (string, non-optional), but the current code at line 88 passes blob.type?.split(";")?.[0] which could be undefined. Since the error check at lines 61–65 guarantees blob.type is truthy before reaching line 88, the refactoring is safe and fixes the type mismatch.

♻️ Suggested refactor
-  const extension = blob.type.split("/")[1]?.split(";")?.[0];
+  const mimeType: string = blob.type.split(";")[0];
+  const extension = mimeType.split("/")[1];
@@
-    mime_type: blob.type?.split(";")?.[0],
+    mime_type: mimeType,

Also applies to: 82–89

🤖 Prompt for AI Agents
In `@src/utils/upload-utils.ts` around lines 59 - 67, Derive a non-optional
mimeType from blob.type once and reuse it instead of optional-chaining at call
sites: compute const mimeType = blob.type.split(";")[0] (after the existing
check that blob.type is truthy), keep the existing extension logic (extension =
blob.type.split("/")[1]?.split(";")?.[0] can be adjusted to use mimeType where
appropriate), and then pass mimeType into the CreateFileRequest mime_type field
(replace blob.type?.split(";")?.[0] with mimeType) so the type is strictly
string and no optional chaining remains; update any other places (e.g., around
the name and file creation) to use this single mimeType variable.

const filename = Date.now().toString();

let length = undefined;
let length: number | undefined;
if (type === ScribeFileType.AUDIO) {
const arrayBuffer = await blob.arrayBuffer();
const audioContext = new AudioContext();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
length = Number(audioBuffer.duration.toFixed(2));
try {
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
length = Number(audioBuffer.duration.toFixed(2));
} finally {
await audioContext.close();
}
}

const data = await API.scribe.createFileUpload({
Expand All @@ -82,32 +85,30 @@ export const uploadScribeFile = async (
name: filename,
associating_id: scribeInstanceId,
file_category: category,
mime_type: blob?.type?.split(";")?.[0],
mime_type: blob.type?.split(";")?.[0],
length,
});

await new Promise<void>((resolve, reject) => {
const url = data?.signed_url;
const internal_name = data?.internal_name;
const f = blob;
if (f === undefined) {
reject(Error("No file to upload"));
const url = data.signed_url;
const internalName = data.internal_name;
if (!url || !internalName) {
reject(Error("Missing upload metadata"));
return;
}
const newFile = new File([f], `${internal_name}`, { type: f.type });
const newFile = new File([blob], internalName, { type: blob.type });

const headers = {
"Content-type": newFile?.type?.split(";")?.[0],
"Content-type": newFile.type.split(";")[0],
"Content-disposition": "inline",
};

uploadFile(
url || "",
url,
newFile,
"PUT",
headers,
(xhr: XMLHttpRequest) => (xhr.status === 200 ? resolve() : reject()),
null,
reject,
(e) => reject(Error(e)),
);
Comment on lines 101 to 112
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and view the specific lines
wc -l src/utils/upload-utils.ts

Repository: ohcnetwork/care_scribe_fe

Length of output: 98


🏁 Script executed:

# Read the relevant section of the file
sed -n '90,120p' src/utils/upload-utils.ts

Repository: ohcnetwork/care_scribe_fe

Length of output: 860


🏁 Script executed:

# Find the uploadFile function definition to understand the callback signatures
rg -A 10 "function uploadFile|const uploadFile|export.*uploadFile" src/utils/upload-utils.ts

Repository: ohcnetwork/care_scribe_fe

Length of output: 715


Add explicit header typing and handle undefined error parameter.

The onError callback receives an optional parameter that may be undefined, resulting in a confusing error message. Also, the headers object lacks explicit typing despite the coding guideline to prefer explicit types over inferred types.

Suggested fix
-    const headers = {
+    const headers: Record<string, string> = {
       "Content-type": newFile.type.split(";")[0],
       "Content-disposition": "inline",
     };
@@
-      (e) => reject(Error(e)),
+      (e) => reject(new Error(e ?? "Upload failed")),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const headers = {
"Content-type": newFile?.type?.split(";")?.[0],
"Content-type": newFile.type.split(";")[0],
"Content-disposition": "inline",
};
uploadFile(
url || "",
url,
newFile,
"PUT",
headers,
(xhr: XMLHttpRequest) => (xhr.status === 200 ? resolve() : reject()),
null,
reject,
(e) => reject(Error(e)),
);
const headers: Record<string, string> = {
"Content-type": newFile.type.split(";")[0],
"Content-disposition": "inline",
};
uploadFile(
url,
newFile,
headers,
(xhr: XMLHttpRequest) => (xhr.status === 200 ? resolve() : reject()),
(e) => reject(new Error(e ?? "Upload failed")),
);
🤖 Prompt for AI Agents
In `@src/utils/upload-utils.ts` around lines 101 - 112, The headers object lacks
an explicit type and the onError callback passes an optional/undefined parameter
directly to Error; update the headers declaration to an explicit type (e.g.,
Record<string, string> or a suitable Headers type) for the constant named
headers, and change the uploadFile onError callback to accept an optional
parameter (e.g., e?: unknown) and call reject(Error(String(e ?? 'Unknown
error'))) or similar so undefined errors produce a clear message instead of
"Error(undefined)"; keep the existing uploadFile call and onLoad callback
intact.

});
Comment thread
shivankacker marked this conversation as resolved.

Expand Down