Skip to content
Closed
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
206 changes: 182 additions & 24 deletions src/content/core/utils/trackers/friendslist.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,46 @@ async function fetchChatConversationsPage(cursor = null) {
endpoint: endpoint,
useBackground: true,
});
} catch (error) {
} catch {
return null;
}
}

async function fetchAllConversations() {
let allConversations = [];
let cursor = null;
const seenCursors = new Set();
do {
const data = await fetchChatConversationsPage(cursor);
if (!data || !data.conversations) break;
if (!data || !Array.isArray(data.conversations)) {
console.error(
'RoValra: Conversation pagination failed; discarding partial data',
);
return null;
}
allConversations = allConversations.concat(data.conversations);
cursor = data.next_cursor;
const nextCursor = data.next_cursor;
if (
nextCursor !== undefined &&
nextCursor !== null &&
typeof nextCursor !== 'string'
) {
console.error(
'RoValra: Conversation pagination returned an invalid cursor',
);
return null;
}
if (
nextCursor &&
(nextCursor === cursor || seenCursors.has(nextCursor))
) {
console.error(
'RoValra: Conversation pagination returned a repeated cursor',
);
return null;
}
if (nextCursor) seenCursors.add(nextCursor);
cursor = nextCursor;
} while (cursor);
return allConversations;
}
Expand All @@ -89,7 +116,7 @@ export async function fetchFriendsPage(userId, cursor = null) {
endpoint: endpoint,
useBackground: true,
});
} catch (error) {
} catch {
return null;
}
}
Expand All @@ -115,21 +142,23 @@ export async function fetchFriendsCustom(
) {
try {
let endpoint = `/v1/users/${userId}/friends/find`;
if (cursor) params.append('cursor', value);
if (params.size > 0) endpoint += `?${params.toString()}`;
const queryParams = new URLSearchParams(params);
if (cursor) queryParams.set('cursor', cursor);
if (queryParams.size > 0) endpoint += `?${queryParams.toString()}`;
return await callRobloxApiJson({
subdomain: 'friends',
endpoint: endpoint,
useBackground: true,
});
} catch (error) {
} catch {
return null;
}
}

export async function fetchAllTrustedFriends(userId) {
const trustedIds = new Set();
let cursor = null;
const seenCursors = new Set();
try {
do {
let endpoint = `/v1/users/${userId}/friends/find?findFriendsType=FindTrustedFriends`;
Expand All @@ -139,12 +168,39 @@ export async function fetchAllTrustedFriends(userId) {
endpoint,
useBackground: true,
});
if (!response || !response.PageItems) break;
response.PageItems.forEach((item) => trustedIds.add(item.id));
cursor = response.NextCursor;
if (
!response ||
!Array.isArray(response.PageItems) ||
response.PageItems.some(
(friend) =>
!friend ||
(typeof friend.id !== 'number' &&
typeof friend.id !== 'string'),
)
) {
return null;
}
response.PageItems.forEach((item) => trustedIds.add(String(item.id)));
const nextCursor = response.NextCursor;
if (
nextCursor !== undefined &&
nextCursor !== null &&
typeof nextCursor !== 'string'
) {
return null;
}
if (
nextCursor &&
(nextCursor === cursor || seenCursors.has(nextCursor))
) {
return null;
}
if (nextCursor) seenCursors.add(nextCursor);
cursor = nextCursor;
} while (cursor);
} catch (error) {
console.error('RoValra: Failed to fetch all trusted friends', error);
return null;
}
return trustedIds;
}
Expand All @@ -156,7 +212,7 @@ export async function fetchFriendsOnlineStatus(userId) {
endpoint: `/v1/users/${userId}/friends/online`,
useBackground: true,
});
return response?.data || [];
return Array.isArray(response?.data) ? response.data : [];
} catch (error) {
console.error('RoValra: Failed to fetch online status', error);
return [];
Expand Down Expand Up @@ -184,9 +240,8 @@ export async function updateFriendsList(userId) {
chrome.storage.local.get([FRIENDS_DATA_KEY], resolve),
);
const allUsersFriendsData = storageResult[FRIENDS_DATA_KEY] || {};
const existingMap = new Map(
(allUsersFriendsData[userId]?.friendsList || []).map((f) => [f.id, f]),
);
const existingFriendsList = allUsersFriendsData[userId]?.friendsList || [];
const existingMap = new Map(existingFriendsList.map((f) => [f.id, f]));

try {
const [friendsCount, conversations, onlineData, allTrustedFriendsSet] =
Expand All @@ -197,6 +252,13 @@ export async function updateFriendsList(userId) {
fetchAllTrustedFriends(userId),
]);

if (!(allTrustedFriendsSet instanceof Set)) {
console.error(
'RoValra: Trusted friends refresh failed; keeping existing cache',
);
return existingFriendsList;
}

const onlineMap = new Map();
onlineData.forEach((item) => {
onlineMap.set(item.id, {
Expand Down Expand Up @@ -257,18 +319,89 @@ export async function updateFriendsList(userId) {
});
}

const seenCursors = new Set();

do {
const page = await fetchFriendsPage(userId, friendsCursor);
if (!page || !page.PageItems) break;

if (
!page ||
!Array.isArray(page.PageItems) ||
page.PageItems.some(
(friend) =>
!friend ||
(typeof friend.id !== 'number' &&
typeof friend.id !== 'string'),
)
) {
console.error(
'RoValra: Friends pagination failed; keeping existing cache',
);
return existingFriendsList;
}

const nextCursor = page.NextCursor;
if (
nextCursor !== undefined &&
nextCursor !== null &&
typeof nextCursor !== 'string'
) {
console.error(
'RoValra: Friends pagination returned an invalid cursor; keeping existing cache',
);
return existingFriendsList;
}
if (
nextCursor &&
(nextCursor === friendsCursor || seenCursors.has(nextCursor))
) {
console.error(
'RoValra: Friends pagination returned a repeated cursor; keeping existing cache',
);
return existingFriendsList;
}

allFriends = allFriends.concat(page.PageItems);
friendsCursor = page.NextCursor;
if (nextCursor) seenCursors.add(nextCursor);
friendsCursor = nextCursor;
} while (friendsCursor);

const uniqueFriendsById = new Map();
allFriends.forEach((friend) => {
if (
friend &&
(typeof friend.id === 'number' ||
typeof friend.id === 'string') &&
!uniqueFriendsById.has(String(friend.id))
) {
uniqueFriendsById.set(String(friend.id), friend);
}
});
const uniqueFriends = [...uniqueFriendsById.values()];

if (friendsCount !== null && uniqueFriends.length < friendsCount) {
console.error(
'RoValra: Friends pagination returned fewer unique friends than expected; keeping existing cache',
);
return existingFriendsList;
}

if (
friendsCount === null &&
uniqueFriends.length === 0 &&
existingFriendsList.length > 0
) {
console.error(
'RoValra: Empty friends response without a verified count; keeping existing cache',
);
return existingFriendsList;
}

const batchSize = 50;
let fullFriendsList = [];

for (let i = 0; i < allFriends.length; i += batchSize) {
const batchIds = allFriends
for (let i = 0; i < uniqueFriends.length; i += batchSize) {
const batchIds = uniqueFriends
.slice(i, i + batchSize)
.map((f) => f.id);
const [profileData, insightsData, playedTogetherData] =
Expand Down Expand Up @@ -301,6 +434,20 @@ export async function updateFriendsList(userId) {
});
}

if (
!Array.isArray(profileData?.profileDetails) ||
!batchIds.every((id) =>
profileData.profileDetails.some(
(profile) => String(profile.userId) === String(id),
),
)
) {
console.error(
'RoValra: Failed to fetch complete friend profiles; keeping existing cache',
);
return existingFriendsList;
}

if (profileData?.profileDetails) {
const deletedUserIds = profileData.profileDetails
.filter((profile) => profile.isDeleted)
Expand All @@ -317,7 +464,7 @@ export async function updateFriendsList(userId) {
const enrichedFriends = profileData.profileDetails.map(
(profile) => {
const friendId = profile.userId;
const isTrusted = allTrustedFriendsSet.has(friendId);
const isTrusted = allTrustedFriendsSet.has(String(friendId));
const chatStatus = chatAnalysisMap.get(friendId);
const userInsights = insightMap.get(friendId) || [];
const playedTogetherInsights =
Expand Down Expand Up @@ -424,8 +571,18 @@ export async function updateFriendsList(userId) {
profile.hasRobloxSubscription,
isDeleted: profile.isDeleted,
isTrusted: isTrusted,
canChat: chatStatus?.canChat ?? null,
hasAgeChecked: chatStatus?.hasAgeChecked ?? null,
canChat:
chatStatus?.canChat ??
(conversations === null
? existingFriend?.canChat
: null) ??
null,
hasAgeChecked:
chatStatus?.hasAgeChecked ??
(conversations === null
? existingFriend?.hasAgeChecked
: null) ??
null,
mutualFriends: mutualFriends,
accountCreated: accountCreated,
friendsSince: friendsSince,
Expand Down Expand Up @@ -469,7 +626,7 @@ export async function updateFriendsList(userId) {
return fullFriendsList;
} catch (error) {
console.error('RoValra: Failed to update friends list', error);
return [];
return existingFriendsList;
}
}

Expand Down Expand Up @@ -517,17 +674,18 @@ async function updateOnlineStatusOnly(userId, currentFriendsList) {
);

return updatedList;
} catch (error) {
} catch {
return currentFriendsList;
}
}

async function updateTrustedFriendsOnly(userId, currentFriendsList) {
try {
const trustedIds = await fetchAllTrustedFriends(userId);
if (!(trustedIds instanceof Set)) return currentFriendsList;
const updatedList = currentFriendsList.map((friend) => ({
...friend,
isTrusted: trustedIds.has(friend.id),
isTrusted: trustedIds.has(String(friend.id)),
}));

const storageResult = await new Promise((resolve) =>
Expand Down
Loading