@@ -34,7 +34,7 @@ import { randomString, secureRandomBase64Url } from "../randomstring.ts";
3434import { EncryptionKeysEventContent } from "./types.ts" ;
3535import { decodeBase64 , encodeUnpaddedBase64 } from "../base64.ts" ;
3636import { KnownMembership } from "../@types/membership.ts" ;
37- import { MatrixError , safeGetRetryAfterMs } from "../http-api/errors.ts" ;
37+ import { HTTPError , MatrixError , safeGetRetryAfterMs } from "../http-api/errors.ts" ;
3838import { MatrixEvent } from "../models/event.ts" ;
3939import { isLivekitFocusActive } from "./LivekitFocus.ts" ;
4040import { ExperimentalGroupCallRoomMemberState } from "../webrtc/groupCall.ts" ;
@@ -1031,39 +1031,39 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
10311031 const prepareDelayedDisconnection = async ( ) : Promise < void > => {
10321032 try {
10331033 // TODO: If delayed event times out, re-join!
1034- const res = await this . client . _unstable_sendDelayedStateEvent (
1035- this . room . roomId ,
1036- {
1037- delay : 8000 ,
1038- } ,
1039- EventType . GroupCallMemberPrefix ,
1040- { } , // leave event
1041- stateKey ,
1034+ const res = await resendIfRateLimited ( ( ) =>
1035+ this . client . _unstable_sendDelayedStateEvent (
1036+ this . room . roomId ,
1037+ {
1038+ delay : 8000 ,
1039+ } ,
1040+ EventType . GroupCallMemberPrefix ,
1041+ { } , // leave event
1042+ stateKey ,
1043+ ) ,
10421044 ) ;
10431045 this . disconnectDelayId = res . delay_id ;
10441046 } catch ( e ) {
1045- // TODO: Retry if rate-limited
10461047 logger . error ( "Failed to prepare delayed disconnection event:" , e ) ;
10471048 }
10481049 } ;
10491050 await prepareDelayedDisconnection ( ) ;
10501051 // Send join event _after_ preparing the delayed disconnection event
1051- await this . client . sendStateEvent (
1052- this . room . roomId ,
1053- EventType . GroupCallMemberPrefix ,
1054- newContent ,
1055- stateKey ,
1052+ await resendIfRateLimited ( ( ) =>
1053+ this . client . sendStateEvent ( this . room . roomId , EventType . GroupCallMemberPrefix , newContent , stateKey ) ,
10561054 ) ;
10571055 // If sending state cancels your own delayed state, prepare another delayed state
10581056 // TODO: Remove this once MSC4140 is stable & doesn't cancel own delayed state
10591057 if ( this . disconnectDelayId !== undefined ) {
10601058 try {
1061- await this . client . _unstable_updateDelayedEvent (
1062- this . disconnectDelayId ,
1063- UpdateDelayedEventAction . Restart ,
1059+ const knownDisconnectDelayId = this . disconnectDelayId ;
1060+ await resendIfRateLimited ( ( ) =>
1061+ this . client . _unstable_updateDelayedEvent (
1062+ knownDisconnectDelayId ,
1063+ UpdateDelayedEventAction . Restart ,
1064+ ) ,
10641065 ) ;
10651066 } catch ( e ) {
1066- // TODO: Make embedded client include errcode, and retry only if not M_NOT_FOUND (or rate-limited)
10671067 logger . warn ( "Failed to update delayed disconnection event, prepare it again:" , e ) ;
10681068 this . disconnectDelayId = undefined ;
10691069 await prepareDelayedDisconnection ( ) ;
@@ -1076,23 +1076,27 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
10761076 let sentDelayedDisconnect = false ;
10771077 if ( this . disconnectDelayId !== undefined ) {
10781078 try {
1079- await this . client . _unstable_updateDelayedEvent (
1080- this . disconnectDelayId ,
1081- UpdateDelayedEventAction . Send ,
1079+ const knownDisconnectDelayId = this . disconnectDelayId ;
1080+ await resendIfRateLimited ( ( ) =>
1081+ this . client . _unstable_updateDelayedEvent (
1082+ knownDisconnectDelayId ,
1083+ UpdateDelayedEventAction . Send ,
1084+ ) ,
10821085 ) ;
10831086 sentDelayedDisconnect = true ;
10841087 } catch ( e ) {
1085- // TODO: Retry if rate-limited
10861088 logger . error ( "Failed to send our delayed disconnection event:" , e ) ;
10871089 }
10881090 this . disconnectDelayId = undefined ;
10891091 }
10901092 if ( ! sentDelayedDisconnect ) {
1091- await this . client . sendStateEvent (
1092- this . room . roomId ,
1093- EventType . GroupCallMemberPrefix ,
1094- { } ,
1095- this . makeMembershipStateKey ( localUserId , localDeviceId ) ,
1093+ await resendIfRateLimited ( ( ) =>
1094+ this . client . sendStateEvent (
1095+ this . room . roomId ,
1096+ EventType . GroupCallMemberPrefix ,
1097+ { } ,
1098+ this . makeMembershipStateKey ( localUserId , localDeviceId ) ,
1099+ ) ,
10961100 ) ;
10971101 }
10981102 }
@@ -1111,10 +1115,12 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
11111115
11121116 private readonly delayDisconnection = async ( ) : Promise < void > => {
11131117 try {
1114- await this . client . _unstable_updateDelayedEvent ( this . disconnectDelayId ! , UpdateDelayedEventAction . Restart ) ;
1118+ const knownDisconnectDelayId = this . disconnectDelayId ! ;
1119+ await resendIfRateLimited ( ( ) =>
1120+ this . client . _unstable_updateDelayedEvent ( knownDisconnectDelayId , UpdateDelayedEventAction . Restart ) ,
1121+ ) ;
11151122 this . scheduleDelayDisconnection ( ) ;
11161123 } catch ( e ) {
1117- // TODO: Retry if rate-limited
11181124 logger . error ( "Failed to delay our disconnection event:" , e ) ;
11191125 }
11201126 } ;
@@ -1162,3 +1168,31 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
11621168 this . sendEncryptionKeysEvent ( newKeyIndex ) ;
11631169 } ;
11641170}
1171+
1172+ async function resendIfRateLimited < T > ( func : ( ) => Promise < T > , numRetriesAllowed : number = 1 ) : Promise < T > {
1173+ // eslint-disable-next-line no-constant-condition
1174+ while ( true ) {
1175+ try {
1176+ return await func ( ) ;
1177+ } catch ( e ) {
1178+ if ( numRetriesAllowed > 0 && e instanceof HTTPError && e . isRateLimitError ( ) ) {
1179+ numRetriesAllowed -- ;
1180+ let resendDelay : number ;
1181+ const defaultMs = 5000 ;
1182+ try {
1183+ resendDelay = e . getRetryAfterMs ( ) ?? defaultMs ;
1184+ logger . info ( `Rate limited by server, retrying in ${ resendDelay } ms` ) ;
1185+ } catch ( e ) {
1186+ logger . warn (
1187+ `Error while retrieving a rate-limit retry delay, retrying after default delay of ${ defaultMs } ` ,
1188+ e ,
1189+ ) ;
1190+ resendDelay = defaultMs ;
1191+ }
1192+ await sleep ( resendDelay ) ;
1193+ } else {
1194+ throw e ;
1195+ }
1196+ }
1197+ }
1198+ }
0 commit comments