@@ -14,8 +14,6 @@ import { CreateSectionDialog } from "../../components/views/dialogs/CreateSectio
1414import { RemoveSectionDialog } from "../../components/views/dialogs/RemoveSectionDialog" ;
1515import { DefaultTagID , type TagID } from "./skip-list/tag" ;
1616
17- type Tag = string ;
18-
1917/**
2018 * A synthetic tag used to represent the "Chats" section, which contains
2119 * every room that does not belong to any other explicit tag section.
@@ -27,12 +25,14 @@ export const CHATS_TAG = "chats";
2725 */
2826export const CUSTOM_SECTION_TAG_PREFIX = "element.io.section." ;
2927
28+ type CustomTag = `${typeof CUSTOM_SECTION_TAG_PREFIX } ${string } `;
29+
3030/**
3131 * Checks if a given tag is a custom section tag.
3232 * @param tag - The tag to check.
3333 * @returns True if the tag is a custom section tag, false otherwise.
3434 */
35- export function isCustomSectionTag ( tag : string ) : boolean {
35+ export function isCustomSectionTag ( tag : string ) : tag is CustomTag {
3636 return tag . startsWith ( CUSTOM_SECTION_TAG_PREFIX ) ;
3737}
3838
@@ -58,18 +58,56 @@ export function isSectionTag(tagId: TagID): boolean {
5858 * Structure of the custom section stored in the settings. The tag is used as a unique identifier for the section, and the name is given by the user.
5959 */
6060type CustomSection = {
61- tag : Tag ;
61+ tag : CustomTag ;
6262 name : string ;
6363} ;
6464
65+ /**
66+ * Type guard to check if a value is a valid CustomSection object.
67+ */
68+ function isValidCustomSection ( value : unknown ) : value is CustomSection {
69+ return (
70+ typeof value === "object" &&
71+ value !== null &&
72+ isCustomSectionTag ( ( value as Record < string , unknown > ) . tag as string ) &&
73+ typeof ( value as Record < string , unknown > ) . name === "string"
74+ ) ;
75+ }
76+
6577/**
6678 * The custom sections data is stored as a record in the settings, where the key is the section tag and the value is the section data (name and tag).
6779 */
68- export type CustomSectionsData = Record < Tag , CustomSection > ;
80+ export type CustomSectionsData = Record < CustomTag , CustomSection > ;
6981/**
7082 * Ordered list of custom section tags.
7183 */
72- export type OrderedCustomSections = Tag [ ] ;
84+ export type OrderedCustomSections = CustomTag [ ] ;
85+
86+ /**
87+ * Retrieves the custom sections data from the settings.
88+ * Invalid or malformed entries are dropped and the cleaned data is persisted back to settings.
89+ */
90+ export function getCustomSectionData ( ) : CustomSectionsData {
91+ const raw = SettingsStore . getValue ( "RoomList.CustomSectionData" ) ;
92+ // Data are malformed
93+ if ( typeof raw !== "object" || raw === null || Array . isArray ( raw ) ) return { } ;
94+
95+ // Filter out invalid entries
96+ return Object . fromEntries (
97+ Object . entries ( raw ) . filter ( ( [ key , value ] ) => isValidCustomSection ( value ) && value . tag === key ) ,
98+ ) as CustomSectionsData ;
99+ }
100+
101+ /**
102+ * Retrieves the ordered list of custom section tags from the settings.
103+ * If the settings contain tags that are not present in the custom section data, they will be filtered out and the settings will be updated to remove the unknown tags.
104+ */
105+ export function getOrderedCustomSections ( ) : OrderedCustomSections {
106+ const sectionData = getCustomSectionData ( ) ;
107+ const rawValue = SettingsStore . getValue ( "RoomList.OrderedCustomSections" ) ;
108+ const orderedSections : OrderedCustomSections = Array . isArray ( rawValue ) ? rawValue : [ ] ;
109+ return orderedSections . filter ( ( tag ) => tag in sectionData ) ;
110+ }
73111
74112/**
75113 * Creates a new custom section by showing a dialog to the user to enter the section name.
@@ -83,16 +121,16 @@ export async function createSection(): Promise<string | undefined> {
83121 const [ shouldCreateSection , sectionName ] = await modal . finished ;
84122 if ( ! shouldCreateSection || ! sectionName ) return undefined ;
85123
86- const tag = `${ CUSTOM_SECTION_TAG_PREFIX } ${ window . crypto . randomUUID ( ) } ` ;
124+ const tag : CustomTag = `${ CUSTOM_SECTION_TAG_PREFIX } ${ window . crypto . randomUUID ( ) } ` ;
87125 const newSection : CustomSection = { tag, name : sectionName } ;
88126
89127 // Save the new section data
90- const sectionData = SettingsStore . getValue ( "RoomList.CustomSectionData" ) || { } ;
128+ const sectionData = getCustomSectionData ( ) ;
91129 sectionData [ tag ] = newSection ;
92130 await SettingsStore . setValue ( "RoomList.CustomSectionData" , null , SettingLevel . ACCOUNT , sectionData ) ;
93131
94132 // Add the new section to the ordered list of sections
95- const orderedSections = SettingsStore . getValue ( "RoomList.OrderedCustomSections" ) || [ ] ;
133+ const orderedSections = getOrderedCustomSections ( ) ;
96134 orderedSections . push ( tag ) ;
97135 await SettingsStore . setValue ( "RoomList.OrderedCustomSections" , null , SettingLevel . ACCOUNT , orderedSections ) ;
98136 return tag ;
@@ -103,7 +141,11 @@ export async function createSection(): Promise<string | undefined> {
103141 * @param tag - The tag of the section to edit.
104142 */
105143export async function editSection ( tag : string ) : Promise < void > {
106- const sectionData = SettingsStore . getValue ( "RoomList.CustomSectionData" ) || { } ;
144+ if ( ! isCustomSectionTag ( tag ) ) {
145+ logger . info ( "Unknown section tag, cannot edit section" , tag ) ;
146+ return ;
147+ }
148+ const sectionData = getCustomSectionData ( ) ;
107149 const section = sectionData [ tag ] ;
108150 if ( ! section ) {
109151 logger . info ( "Unknown section tag, cannot edit section" , tag ) ;
@@ -127,7 +169,11 @@ export async function editSection(tag: string): Promise<void> {
127169 * @param isEmpty - Whether the section is empty (has no rooms). If the section is not empty, the confirmation dialog will show a warning message.
128170 */
129171export async function deleteSection ( tag : string , isEmpty : boolean ) : Promise < void > {
130- const sectionData = SettingsStore . getValue ( "RoomList.CustomSectionData" ) ;
172+ if ( ! isCustomSectionTag ( tag ) ) {
173+ logger . info ( "Unknown section tag, cannot delete section" , tag ) ;
174+ return ;
175+ }
176+ const sectionData = getCustomSectionData ( ) ;
131177 if ( ! sectionData [ tag ] ) {
132178 logger . info ( "Unknown section tag, cannot delete section" , tag ) ;
133179 return ;
@@ -138,8 +184,7 @@ export async function deleteSection(tag: string, isEmpty: boolean): Promise<void
138184 if ( ! shouldRemoveSection ) return ;
139185
140186 // Remove the section from the ordered list of sections
141- const orderedSections = SettingsStore . getValue ( "RoomList.OrderedCustomSections" ) ;
142- const newOrderedSections = orderedSections . filter ( ( sectionTag ) => sectionTag !== tag ) ;
187+ const newOrderedSections = getOrderedCustomSections ( ) . filter ( ( sectionTag ) => sectionTag !== tag ) ;
143188 await SettingsStore . setValue ( "RoomList.OrderedCustomSections" , null , SettingLevel . ACCOUNT , newOrderedSections ) ;
144189
145190 // Remove the section data
0 commit comments