@@ -77,6 +77,7 @@ export class OlmAdapter extends Listenable {
7777 this . _mediaKeyIndex = - 1 ;
7878 this . _reqs = new Map ( ) ;
7979 this . _sessionInitialization = undefined ;
80+ this . _sessionReadyCallbacks = new Map ( ) ; // participantId -> callback
8081
8182 if ( OlmAdapter . isSupported ( ) ) {
8283 this . _bootstrapOlm ( ) ;
@@ -101,32 +102,64 @@ export class OlmAdapter extends Listenable {
101102 }
102103
103104 /**
104- * Starts new olm sessions with every other participant that has the participantId "smaller" the localParticipantId.
105+ * Starts new olm sessions with every other participant and waits for all sessions to be established.
106+ * This includes both outgoing sessions (initiated by us to participants with higher IDs) and
107+ * incoming sessions (initiated by participants with lower IDs).
105108 */
106109 async initSessions ( ) {
107110 if ( this . _sessionInitialization ) {
108111 throw new Error ( 'OlmAdapter initSessions called multiple times' ) ;
109- } else {
110- this . _sessionInitialization = new Deferred ( ) ;
112+ }
111113
112- await this . _init ;
114+ this . _sessionInitialization = new Deferred ( ) ;
113115
114- const promises = [ ] ;
115- const localParticipantId = this . _conf . myUserId ( ) ;
116+ await this . _init ;
116117
117- for ( const participant of this . _conf . getParticipants ( ) ) {
118- if ( participant . hasFeature ( FEATURE_E2EE ) && localParticipantId < participant . getId ( ) ) {
119- promises . push ( this . _sendSessionInit ( participant ) ) ;
120- }
118+ const localParticipantId = this . _conf . myUserId ( ) ;
119+ const e2eeParticipants = this . _conf . getParticipants ( ) . filter ( p => p . hasFeature ( FEATURE_E2EE ) ) ;
120+ const outgoingParticipants = e2eeParticipants . filter ( p => localParticipantId < p . getId ( ) ) ;
121+
122+ // Send session-init to participants with higher IDs (outgoing sessions)
123+ const outgoingPromises = outgoingParticipants . map ( participant =>
124+ this . _sendSessionInit ( participant )
125+ ) ;
126+
127+ await Promise . allSettled ( outgoingPromises ) ;
128+
129+ // TODO: retry failed ones.
130+
131+ const waitPromises = [ ] ;
132+
133+ for ( const participant of e2eeParticipants ) {
134+ const pId = participant . getId ( ) ;
135+ const olmData = this . _getParticipantOlmData ( participant ) ;
136+
137+ if ( olmData . session ) {
138+ // Session already established
139+ continue ;
121140 }
122141
123- await Promise . allSettled ( promises ) ;
142+ logger . debug ( `Waiting for session with participant ${ pId } ` ) ;
143+ const sessionPromise = new Promise ( resolve => {
144+ this . _sessionReadyCallbacks . set ( pId , resolve ) ;
145+ } ) ;
146+
147+ waitPromises . push ( sessionPromise ) ;
148+ }
124149
125- // TODO: retry failed ones.
150+ if ( waitPromises . length > 0 ) {
151+ logger . debug ( `Waiting for ${ waitPromises . length } sessions to be established` ) ;
126152
127- this . _sessionInitialization . resolve ( ) ;
128- this . _sessionInitialization = undefined ;
153+ await Promise . race ( [
154+ Promise . allSettled ( waitPromises ) ,
155+ new Promise ( resolve => setTimeout ( resolve , 10000 ) ) // 10s timeout
156+ ] ) ;
157+ this . _sessionReadyCallbacks . clear ( ) ;
129158 }
159+
160+ logger . debug ( 'All Olm sessions established (outgoing and incoming)' ) ;
161+ this . _sessionInitialization . resolve ( ) ;
162+ this . _sessionInitialization = undefined ;
130163 }
131164
132165 /**
@@ -207,7 +240,7 @@ export class OlmAdapter extends Listenable {
207240 }
208241
209242 /**
210- * Frees the olmData session for the given participant.
243+ * Frees the olmData session for the given participant and clears all session-related state .
211244 *
212245 */
213246 clearParticipantSession ( participant ) {
@@ -217,6 +250,16 @@ export class OlmAdapter extends Listenable {
217250 olmData . session . free ( ) ;
218251 olmData . session = undefined ;
219252 }
253+
254+ // Clear all session-related state to allow clean re-initialization
255+ olmData . pendingSessionUuid = undefined ;
256+ olmData . lastKey = undefined ;
257+
258+ // Clean up SAS verification if active
259+ if ( olmData . sasVerification ?. sas ) {
260+ olmData . sasVerification . sas . free ( ) ;
261+ }
262+ olmData . sasVerification = undefined ;
220263 }
221264
222265 /**
@@ -227,6 +270,8 @@ export class OlmAdapter extends Listenable {
227270 for ( const participant of this . _conf . getParticipants ( ) ) {
228271 this . clearParticipantSession ( participant ) ;
229272 }
273+ this . _reqs . clear ( ) ;
274+ this . _sessionInitialization = undefined ;
230275 }
231276
232277 /**
@@ -387,6 +432,14 @@ export class OlmAdapter extends Listenable {
387432 */
388433 _onParticipantE2EEChannelReady ( id ) {
389434 logger . debug ( `E2EE channel with participant ${ id } is ready` ) ;
435+
436+ // Notify any waiting promises that this session is ready
437+ const callback = this . _sessionReadyCallbacks . get ( id ) ;
438+
439+ if ( callback ) {
440+ callback ( ) ;
441+ this . _sessionReadyCallbacks . delete ( id ) ;
442+ }
390443 }
391444
392445 /**
@@ -900,10 +953,29 @@ export class OlmAdapter extends Listenable {
900953 const participantFeatures = await participant . getFeatures ( ) ;
901954
902955 if ( participantFeatures . has ( FEATURE_E2EE ) && localParticipantId < participantId ) {
903- if ( this . _sessionInitialization ) {
904- await this . _sessionInitialization ;
956+ let sessionEstablished = false ;
957+
958+ try {
959+ // eslint-disable-next-line max-depth
960+ if ( this . _sessionInitialization ) {
961+ await this . _sessionInitialization ;
962+ }
963+ await this . _sendSessionInit ( participant ) ;
964+ sessionEstablished = true ;
965+ } catch ( error ) {
966+ // Handle specific error cases
967+ // eslint-disable-next-line max-depth
968+ if ( error . message === E2EEErrors . E2EE_OLM_SESSION_ALREADY_EXISTS ) {
969+ sessionEstablished = true ;
970+ } else if ( error . message !== E2EEErrors . E2EE_OLM_SESSION_INIT_PENDING ) {
971+ logger . error ( `Failed to establish Olm session with ${ participantId } :` , error ) ;
972+ }
973+ }
974+
975+ // Only proceed with KEY_INFO if session is ready
976+ if ( ! sessionEstablished ) {
977+ return ;
905978 }
906- await this . _sendSessionInit ( participant ) ;
907979
908980 const uuid = uuidv4 ( ) ;
909981
@@ -986,13 +1058,13 @@ export class OlmAdapter extends Listenable {
9861058 if ( olmData . session ) {
9871059 logger . warn ( `Tried to send session-init to ${ pId } but we already have a session` ) ;
9881060
989- return Promise . reject ( ) ;
1061+ return Promise . reject ( new Error ( E2EEErrors . E2EE_OLM_SESSION_ALREADY_EXISTS ) ) ;
9901062 }
9911063
9921064 if ( olmData . pendingSessionUuid !== undefined ) {
9931065 logger . warn ( `Tried to send session-init to ${ pId } but we already have a pending session` ) ;
9941066
995- return Promise . reject ( ) ;
1067+ return Promise . reject ( new Error ( E2EEErrors . E2EE_OLM_SESSION_INIT_PENDING ) ) ;
9961068 }
9971069
9981070 // Generate a One Time Key.
@@ -1002,7 +1074,7 @@ export class OlmAdapter extends Listenable {
10021074 const otKey = Object . values ( otKeys . curve25519 ) [ 0 ] ;
10031075
10041076 if ( ! otKey ) {
1005- return Promise . reject ( new Error ( 'No one-time-keys generated' ) ) ;
1077+ return Promise . reject ( new Error ( E2EEErrors . E2EE_OLM_NO_ONE_TIME_KEYS ) ) ;
10061078 }
10071079
10081080 // Mark the OT keys (one really) as published so they are not reused.
0 commit comments