11// Controls the logic for determining which membership lists should be synced and
22// handles the sequence of events until the lists are in sync.
33
4- import Bluebird from "bluebird" ;
54import { IrcBridge } from "./IrcBridge" ;
65import { AppServiceBot , MembershipQueue } from "matrix-appservice-bridge" ;
76import { IrcServer } from "../irc/IrcServer" ;
@@ -35,8 +34,15 @@ interface LeaveQueueItem {
3534 userIds : string [ ] ;
3635}
3736
37+ interface MemberJoinEntry {
38+ roomId : string ;
39+ displayName : string ;
40+ userId : string ;
41+ frontier : boolean
42+ }
43+
3844type InjectJoinFn = ( roomId : string , joiningUserId : string ,
39- displayName : string , isFrontier : boolean ) => PromiseLike < unknown > ;
45+ displayName : string , isFrontier : boolean ) => PromiseLike < boolean > ;
4046
4147export class MemberListSyncer {
4248 private syncableRoomsPromise : Promise < RoomInfo [ ] > | null = null ;
@@ -46,6 +52,7 @@ export class MemberListSyncer {
4652 irc : { [ channel : string ] : string [ ] } ;
4753 matrix : { [ roomId : string ] : RoomInfo } ;
4854 } = { irc : { } , matrix : { } } ;
55+ private memberEntriesToSync ?: MemberJoinEntry [ ] ;
4956
5057 constructor ( private ircBridge : IrcBridge , private memberQueue : MembershipQueue ,
5158 private appServiceBot : AppServiceBot , private server : IrcServer ,
@@ -64,9 +71,9 @@ export class MemberListSyncer {
6471 log . info ( "Found %s syncable rooms (%sms)" , rooms . length , Date . now ( ) - start ) ;
6572 this . leaveIrcUsersFromRooms ( rooms ) ;
6673 start = Date . now ( ) ;
67- log . info ( "Joining Matrix users to IRC channels ..." ) ;
68- await this . joinMatrixUsersToChannels ( rooms , this . injectJoinFn ) ;
69- log . info ( "Joined Matrix users to IRC channels . (%sms)" , Date . now ( ) - start ) ;
74+ log . info ( "Collecting all Matrix users in all channel rooms ..." ) ;
75+ await this . collectMatrixUsersToJoinToChannels ( rooms ) ;
76+ log . info ( "Collected all Matrix users in all channel rooms . (%sms)" , Date . now ( ) - start ) ;
7077 // NB: We do not need to explicitly join IRC users to Matrix rooms
7178 // because we get all of the NAMEs/JOINs as events when we connect to
7279 // the IRC server. This effectively "injects" the list for us.
@@ -234,8 +241,7 @@ export class MemberListSyncer {
234241 return this . syncableRoomsPromise ;
235242 }
236243
237- private async joinMatrixUsersToChannels ( rooms : RoomInfo [ ] , injectJoinFn : InjectJoinFn ) {
238-
244+ private async collectMatrixUsersToJoinToChannels ( rooms : RoomInfo [ ] ) {
239245 // filter out rooms listed in the rules
240246 const filteredRooms : RoomInfo [ ] = [ ] ;
241247 rooms . forEach ( ( roomInfo ) => {
@@ -260,7 +266,7 @@ export class MemberListSyncer {
260266
261267 // map the filtered rooms to a list of users to join
262268 // [Room:{reals:[uid,uid]}, ...] => [{uid,roomid}, ...]
263- const entries : { roomId : string ; displayName : string ; userId : string ; frontier : boolean } [ ] = [ ] ;
269+ const entries : MemberJoinEntry [ ] = [ ] ;
264270 const idleRegex = this . server . ignoreIdleUsersOnStartupExcludeRegex ;
265271 for ( const roomInfo of filteredRooms ) {
266272 for ( const uid of roomInfo . realJoinedUsers ) {
@@ -300,37 +306,52 @@ export class MemberListSyncer {
300306 } ) ;
301307
302308 log . debug ( "Got %s matrix join events to inject." , entries . length ) ;
309+ this . memberEntriesToSync = entries ;
310+ }
311+
312+ public async joinMatrixUsersToChannels ( ) {
313+ const start = Date . now ( ) ;
314+ log . info ( "Joining all Matrix users in all channel rooms" ) ;
315+ const entries = this . memberEntriesToSync ;
316+ if ( entries === undefined ) {
317+ // Can be expected if syncing is off.
318+ log . info ( `joinMatrixUsersToChannels: No entries collected for joining` ) ;
319+ return ;
320+ }
303321 this . usersToJoin = entries . length ;
304- const d = promiseutil . defer ( ) ;
305- // take the first entry and inject a join event
306- const joinNextUser = ( ) => {
307- const entry = entries . shift ( ) ;
308- if ( ! entry ) {
309- d . resolve ( ) ;
310- return ;
311- }
322+
323+ let entry : MemberJoinEntry | undefined ;
324+ const floodDelayMs = this . server . getMemberListFloodDelayMs ( ) ;
325+ // eslint-disable-next-line no-cond-assign
326+ while ( entry = entries . shift ( ) ) {
312327 this . usersToJoin -- ;
313328 if ( entry . userId . startsWith ( "@-" ) ) {
314- joinNextUser ( ) ;
315- return ;
329+ // Ignore guest users.
330+ continue ;
316331 }
317332 log . debug (
318333 "Injecting join event for %s in %s (%s left) is_frontier=%s" ,
319334 entry . userId , entry . roomId , entries . length , entry . frontier
320335 ) ;
321- Bluebird . cast ( injectJoinFn ( entry . roomId , entry . userId , entry . displayName , entry . frontier ) ) . timeout (
322- this . server . getMemberListFloodDelayMs ( )
323- ) . then ( ( ) => {
324- joinNextUser ( ) ;
325- } ) . catch ( ( ) => {
326- // discard error, this will be due to timeouts which we don't want to log
327- joinNextUser ( ) ;
328- } )
336+ try {
337+ // Inject a join to connect the user. We wait up til the delay time,
338+ // and then just connect the next user.
339+ // If the user connects *faster* than the delay time, then we need
340+ // to delay for the remainder.
341+ const delayPromise = promiseutil . delay ( floodDelayMs ) ;
342+ await Promise . race ( [
343+ delayPromise ,
344+ this . injectJoinFn ( entry . roomId , entry . userId , entry . displayName , entry . frontier ) ,
345+ ] ) ;
346+ await delayPromise ;
347+ }
348+ catch ( ex ) {
349+ // injectJoinFn may fail due to failure to get a client (user may be banned)
350+ // or any other reason, we should continue to iterate regardless
351+ log . debug ( `Failed to inject join for ${ entry . userId } ${ entry . roomId } ` , ex ) ;
352+ }
329353 }
330-
331- joinNextUser ( ) ;
332-
333- return d . promise ;
354+ log . info ( "Joining all Matrix users in all channel rooms. (%sms)" , Date . now ( ) - start ) ;
334355 }
335356
336357 public leaveIrcUsersFromRooms ( rooms : RoomInfo [ ] ) {
0 commit comments