Skip to content
Draft
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 @@ -4,7 +4,7 @@ import { OverleafComment } from "../../../../pkg/gen/apiclient/project/v1/projec
import { useSocketStore } from "../../../../stores/socket-store";
import { addClickedOverleafComment, hasClickedOverleafComment } from "../../../../libs/helpers";
import { acceptComments } from "../../../../query/api";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../../../libs/protobuf-utils";
import { CommentsAcceptedRequestSchema } from "../../../../pkg/gen/apiclient/comment/v1/comment_pb";
import { useConversationStore } from "../../../../stores/conversation/conversation-store";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fromJson, JsonValue } from "@bufbuild/protobuf";
import { JsonValue } from "@bufbuild/protobuf";
import { fromJson } from "../../../../libs/protobuf-utils";
import { OverleafCommentSchema } from "../../../../pkg/gen/apiclient/project/v1/project_pb";
import { getProjectId } from "../../../../libs/helpers";
import { useEffect, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PaperScoreResultSchema } from "../../../pkg/gen/apiclient/project/v1/project_pb";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../../libs/protobuf-utils";
import { LoadingIndicator } from "../../loading-indicator";
import { logError } from "../../../libs/logger";
import { cn } from "@heroui/react";
Expand Down
2 changes: 1 addition & 1 deletion webapp/_webapp/src/hooks/useSendMessageStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
StreamPartEnd,
} from "../pkg/gen/apiclient/chat/v2/chat_pb";
import { MessageEntry, MessageEntryStatus } from "../stores/conversation/types";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../libs/protobuf-utils";
import { useConversationStore } from "../stores/conversation/conversation-store";
import { useListConversationsQuery } from "../query";
import { useSocketStore } from "../stores/socket-store";
Expand Down
3 changes: 2 additions & 1 deletion webapp/_webapp/src/libs/apiclient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from "axios";
import { fromJson, JsonValue } from "@bufbuild/protobuf";
import { JsonValue } from "@bufbuild/protobuf";
import { fromJson } from "./protobuf-utils";
import { RefreshTokenResponseSchema } from "../pkg/gen/apiclient/auth/v1/auth_pb";
import { GetUserResponseSchema } from "../pkg/gen/apiclient/user/v1/user_pb";
import { EventEmitter } from "events";
Expand Down
74 changes: 74 additions & 0 deletions webapp/_webapp/src/libs/protobuf-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Test file to demonstrate that the protobuf-utils wrapper handles unknown fields gracefully.
*
* This test can be run manually to verify the fix. Since the project doesn't have
* a test runner configured, this serves as documentation of the expected behavior.
*
* To test manually:
* 1. Add a new field to a protobuf schema on the backend
* 2. Deploy the backend
* 3. Use an older version of the webapp (without regenerating protobuf files)
* 4. Verify that the webapp doesn't crash when receiving the new field
*/

import { fromJson } from "./protobuf-utils";
import { MessageSchema } from "../pkg/gen/apiclient/chat/v2/chat_pb";

/**
* Example: Testing that fromJson ignores unknown fields
*
* This would simulate a backend returning a message with a new field
* that doesn't exist in the current schema.
*/
function testIgnoreUnknownFields() {
// Simulate JSON response from backend with an extra field "newField"
const jsonWithUnknownField = {
messageId: "test-123",
payload: {
user: {
content: "Hello",
selectedText: "",
newFieldThatDoesntExistYet: "This is a new field from a newer backend version",
},
},
timestamp: "0",
};

try {
// This should NOT throw an error even though "newFieldThatDoesntExistYet" doesn't exist in the schema
const message = fromJson(MessageSchema, jsonWithUnknownField);
console.log("✓ Successfully parsed message with unknown field");
console.log(" Message ID:", message.messageId);
console.log(" User content:", message.payload.user?.content);
return true;
} catch (error) {
console.error("✗ Failed to parse message with unknown field:", error);
return false;
}
}

/**
* Example: Testing that fromJson still validates required fields
*/
function testRequiredFieldsStillValidated() {
// Missing required messageId field
const invalidJson = {
payload: {
user: {
content: "Hello",
},
},
};

try {
const message = fromJson(MessageSchema, invalidJson);
console.log("✓ Parsed message (messageId will be empty string):", message.messageId);
return true;
} catch (error) {
console.error("✗ Failed to parse message:", error);
return false;
}
}

// Export test functions for manual testing
export { testIgnoreUnknownFields, testRequiredFieldsStillValidated };
17 changes: 17 additions & 0 deletions webapp/_webapp/src/libs/protobuf-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DescMessage, fromJson as bufFromJson, JsonValue } from "@bufbuild/protobuf";

/**
* Wrapper around fromJson that ignores unknown fields to prevent crashes
* when new fields are added to the schema.
*
* This allows forward compatibility - older webapp versions can work with
* newer backend versions that introduce new fields.
*/
export function fromJson<Desc extends DescMessage>(
schema: Desc,
json: JsonValue,
): InstanceType<Desc["message"]> {
return bufFromJson(schema, json, {
ignoreUnknownFields: true,
});
}
2 changes: 1 addition & 1 deletion webapp/_webapp/src/query/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
GetUserInstructionsRequest,
} from "../pkg/gen/apiclient/user/v1/user_pb";
import { PlainMessage } from "./types";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../libs/protobuf-utils";
import { processStream } from "./utils";
import { CommentsAcceptedRequest, CommentsAcceptedResponseSchema } from "../pkg/gen/apiclient/comment/v1/comment_pb";

Expand Down
3 changes: 2 additions & 1 deletion webapp/_webapp/src/query/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DescMessage, fromJson, JsonValue, JsonWriteOptions, toJson } from "@bufbuild/protobuf";
import { DescMessage, JsonValue, JsonWriteOptions, toJson } from "@bufbuild/protobuf";
import { fromJson } from "../libs/protobuf-utils";
import { logError } from "../libs/logger";
import { useDevtoolStore } from "../stores/devtool-store";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { Conversation, ConversationSchema } from "../../pkg/gen/apiclient/chat/v2/chat_pb";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../libs/protobuf-utils";
import { useConversationUiStore } from "./conversation-ui-store";

interface ConversationStore {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../../libs/protobuf-utils";
import { Conversation, Message, MessageSchema } from "../../../pkg/gen/apiclient/chat/v2/chat_pb";
import { MessageEntry, MessageEntryStatus } from "../types";
import { useStreamingMessageStore } from "../../streaming-message-store";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getProjectId } from "../../../libs/helpers";
import { getCookies } from "../../../intermediate";
import { StreamingMessage } from "../../streaming-message-store";
import { MessageEntry, MessageEntryStatus } from "../types";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../../libs/protobuf-utils";

export async function handleStreamError(
streamError: StreamError,
Expand Down
2 changes: 1 addition & 1 deletion webapp/_webapp/src/views/devtools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button, Input } from "@heroui/react";
import { useStreamingMessageStore } from "../../stores/streaming-message-store";
import { MessageEntry, MessageEntryStatus } from "../../stores/conversation/types";
import { useConversationStore } from "../../stores/conversation/conversation-store";
import { fromJson } from "@bufbuild/protobuf";
import { fromJson } from "../../libs/protobuf-utils";
import { MessageSchema } from "../../pkg/gen/apiclient/chat/v2/chat_pb";
import { isEmptyConversation } from "../chat/helper";
import { useState } from "react";
Expand Down
3 changes: 2 additions & 1 deletion webapp/_webapp/src/views/prompts/prompt-library-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { cn, Spinner } from "@heroui/react";
import { useCallback, useState } from "react";
import { Prompt, PromptSchema } from "../../pkg/gen/apiclient/user/v1/user_pb";
import { ChatButton } from "../chat/header/chat-button";
import { fromJson, toJson } from "@bufbuild/protobuf";
import { toJson } from "@bufbuild/protobuf";
import { fromJson } from "../../libs/protobuf-utils";
import { usePromptLibraryStore } from "../../stores/prompt-library-store";

type PromptLibraryTableProps = {
Expand Down