Merge pull request #1047 from PR0M3TH3AN/codex/introduce-normalizehexid-in-commentthreadservice

Normalize comment thread event ids
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-12-12 11:06:41 -05:00
commit 3375a94a75
2 files changed
+115 -10

No files matched your search

+15 -10
View File
@@ -24,6 +24,11 @@ function normalizeString(value) {
return typeof value === "string" ? value.trim() : "";
}
function normalizeHexId(value) {
const normalized = normalizeString(value);
return normalized ? normalized.toLowerCase() : "";
}
function normalizeRelay(value) {
if (typeof value === "string") {
return value.trim();
@@ -181,7 +186,7 @@ export default class CommentThreadService {
const rawVideoAuthorPubkey = normalizeString(video?.pubkey);
this.videoEventId = normalizeString(video?.id);
this.videoEventId = normalizeHexId(video?.id);
this.videoAddressPointer = buildVideoAddressPointer(video);
this.videoKind = normalizeKind(video?.kind);
this.videoAuthorPubkeyRaw = rawVideoAuthorPubkey;
@@ -194,7 +199,7 @@ export default class CommentThreadService {
this.videoRootRelay = normalizeRelay(
video?.videoRootRelay || video?.rootIdentifierRelay,
);
this.parentCommentId = normalizeString(parentCommentId);
this.parentCommentId = normalizeHexId(parentCommentId);
this.parentCommentKind = this.parentCommentId ? String(COMMENT_EVENT_KIND) : "";
this.parentCommentPubkey = "";
this.activeRelays = Array.isArray(relays) ? [...relays] : null;
@@ -290,7 +295,7 @@ export default class CommentThreadService {
const targetCandidate =
target && typeof target === "object" ? target : {};
const videoEventId = normalizeString(
const videoEventId = normalizeHexId(
targetCandidate.videoEventId || targetCandidate.eventId,
);
@@ -323,7 +328,7 @@ export default class CommentThreadService {
}
getCommentCacheKey(videoEventId) {
const normalized = normalizeString(videoEventId);
const normalized = normalizeHexId(videoEventId);
if (!normalized) {
return "";
}
@@ -507,8 +512,8 @@ export default class CommentThreadService {
return aTime - bTime;
}
const aId = normalizeString(a?.id);
const bId = normalizeString(b?.id);
const aId = normalizeHexId(a?.id);
const bId = normalizeHexId(b?.id);
if (aId && bId) {
return aId.localeCompare(bId);
}
@@ -576,7 +581,7 @@ export default class CommentThreadService {
return null;
}
const eventId = normalizeString(event.id);
const eventId = normalizeHexId(event.id);
if (!eventId) {
return null;
}
@@ -640,7 +645,7 @@ export default class CommentThreadService {
if (name !== "e") {
continue;
}
const normalizedValue = normalizeString(value);
const normalizedValue = normalizeHexId(value);
if (!normalizedValue) {
continue;
}
@@ -929,13 +934,13 @@ export default class CommentThreadService {
}
getCommentIdsForParent(parentId) {
const key = toParentKey(normalizeString(parentId));
const key = toParentKey(normalizeHexId(parentId));
const list = this.childrenByParent.get(key);
return Array.isArray(list) ? [...list] : [];
}
getCommentEvent(commentId) {
const normalized = normalizeString(commentId);
const normalized = normalizeHexId(commentId);
if (!normalized) {
return null;
}
+100
View File
@@ -123,6 +123,106 @@ test("CommentThreadService caches mixed-case video ids consistently", () => {
);
});
test("CommentThreadService normalizes mixed-case event ids in thread state", async () => {
const video = { ...createBaseVideo(), id: "VideoMixed123" };
const topLevelId = "CoMmEnT-Root";
const replyId = "RePlY-Child";
const topLevelEvent = {
id: topLevelId,
kind: 1,
pubkey: "commenterpk",
created_at: 1700000000,
content: "Top level",
tags: [
["e", video.id],
["a", "30078:authorpk:video-root"],
],
};
const replyEvent = {
id: replyId,
kind: 1,
pubkey: "replypk",
created_at: 1700000100,
content: "Reply",
tags: [
["e", video.id.toUpperCase()],
["a", "30078:authorpk:video-root"],
["e", topLevelId.toUpperCase()],
],
};
const fetchVideoComments = async () => [topLevelEvent];
let triggerAppend = null;
const subscribeVideoComments = (target, options) => {
triggerAppend = options?.onEvent;
return () => {};
};
const service = new CommentThreadService({
fetchVideoComments,
subscribeVideoComments,
});
let threadReadyPayload = null;
let appendPayload = null;
service.setCallbacks({
onThreadReady: (payload) => {
threadReadyPayload = payload;
},
onCommentsAppended: (payload) => {
appendPayload = payload;
},
});
await service.loadThread({ video });
assert.equal(
threadReadyPayload.videoEventId,
video.id.toLowerCase(),
"video ids should normalize to lowercase in the thread payload",
);
assert.deepEqual(
threadReadyPayload.topLevelIds,
[topLevelId.toLowerCase()],
"top-level ids should be normalized",
);
assert.equal(
threadReadyPayload.childrenByParent.get(null)[0],
topLevelId.toLowerCase(),
"root children should reference normalized ids",
);
assert.equal(
threadReadyPayload.commentsById.has(topLevelId.toLowerCase()),
true,
"comment map should key by normalized ids",
);
assert.ok(
service.getCommentEvent(topLevelId.toUpperCase()),
"comment lookups should be case-insensitive",
);
assert.equal(typeof triggerAppend, "function", "subscription should be active");
triggerAppend(replyEvent);
assert.deepEqual(
appendPayload.commentIds,
[replyId.toLowerCase()],
"append payload should emit normalized comment ids",
);
assert.equal(
appendPayload.parentCommentId,
topLevelId.toLowerCase(),
"append payload should carry normalized parent ids",
);
assert.deepEqual(
appendPayload.childrenByParent.get(topLevelId.toLowerCase()),
[replyId.toLowerCase()],
"children map should store normalized ids for replies",
);
});
test(
"listVideoComments accepts builder events without parent ids and filters replies",
async () => {