| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2025-10-19 23:10:54 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2025-10-19 23:10:54 UTC |
| parent | 9cebdd625038732f09046c4300dec9094f42502d |
| HaxeCBridge.hx | +2 | -1 |
| borogove/Client.hx | +60 | -50 |
| borogove/EventEmitter.hx | +47 | -14 |
| borogove/EventHandler.hx | +0 | -41 |
| borogove/MessageSync.hx | +2 | -2 |
diff --git a/HaxeCBridge.hx b/HaxeCBridge.hx index 2803fea..9fb3046 100644 --- a/HaxeCBridge.hx +++ b/HaxeCBridge.hx @@ -1716,7 +1716,8 @@ class CConverterContext { } function getTypeAliasCType(type: Type, allowNonTrivial: Bool, allowBareFnTypes: Bool, pos: Position): CType { - var ident = safeIdent(declarationPrefix + '_' + typeDeclarationIdent(type, false)); + //var ident = safeIdent(declarationPrefix + '_' + typeDeclarationIdent(type, false)); + final ident = hx.strings.Strings.toLowerUnderscore(safeIdent(typeDeclarationIdent(type, false))); // order of typedef typeDeclarations should be dependency correct because required typedefs are added before this typedef is added // we call this outside the exists() branch below to make sure `allowNonTrivial` and `allowBareFnTypes` errors will be caught diff --git a/borogove/Client.hx b/borogove/Client.hx index f1d91a5..7b197bc 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -11,7 +11,6 @@ import borogove.Chat; import borogove.ChatMessage; import borogove.Message; import borogove.EventEmitter; -import borogove.EventHandler; import borogove.EncryptionPolicy; #if !NO_OMEMO import borogove.OMEMO; @@ -61,9 +60,6 @@ class Client extends EventEmitter { **/ public var sendAvailable(null, default): Bool = true; private var stream:GenericStream; - private var chatMessageHandlers: Array<(ChatMessage, ChatMessageEvent)->Void> = []; - private var syncMessageHandlers: Array<(ChatMessage)->Void> = []; - private var chatStateHandlers: Array<(String,String,Null<String>,UserState)->Void> = []; @:allow(borogove) private var jid(default,null):JID; private var chats: Array<Chat> = []; @@ -531,9 +527,7 @@ class Client extends EventEmitter { if (userState != null) { final chat = getChat(from.asBare().asString()); if (chat == null || !chat.getParticipantDetails(message.senderId).isSelf) { - for (handler in chatStateHandlers) { - handler(message.senderId, message.chatId, message.threadId, userState); - } + this.trigger("chat-state/update", { message: message, userState: userState }); } } } @@ -1117,9 +1111,10 @@ class Client extends EventEmitter { Event fired when client needs a password for authentication @param handler takes one argument, the Client that needs a password + @returns token for use with removeEventListener **/ public function addPasswordNeededListener(handler:Client->Void) { - this.on("auth/password-needed", (data) -> { + return this.on("auth/password-needed", (data) -> { handler(this); return EventHandled; }); @@ -1129,9 +1124,10 @@ class Client extends EventEmitter { Event fired when client is connected and fully synchronized @param handler takes no arguments + @returns token for use with removeEventListener **/ - public function addStatusOnlineListener(handler:()->Void):Void { - this.on("status/online", (data) -> { + public function addStatusOnlineListener(handler:()->Void) { + return this.on("status/online", (data) -> { handler(); return EventHandled; }); @@ -1141,9 +1137,10 @@ class Client extends EventEmitter { Event fired when client is disconnected @param handler takes no arguments + @returns token for use with removeEventListener **/ - public function addStatusOfflineListener(handler:()->Void):Void { - this.on("status/offline", (data) -> { + public function addStatusOfflineListener(handler:()->Void) { + return this.on("status/offline", (data) -> { handler(); return EventHandled; }); @@ -1153,9 +1150,10 @@ class Client extends EventEmitter { Event fired when connection fails with a fatal error and will not be retried @param handler takes no arguments + @returns token for use with removeEventListener **/ - public function addConnectionFailedListener(handler:()->Void):Void { - stream.on("status/error", (data) -> { + public function addConnectionFailedListener(handler:()->Void) { + return stream.on("status/error", (data) -> { handler(); return EventHandled; }); @@ -1165,9 +1163,10 @@ class Client extends EventEmitter { Event fired when TLS checks fail, to give client the chance to override @param handler takes two arguments, the PEM of the cert and an array of DNS names, and must return true to accept or false to reject + @returns token for use with removeEventListener **/ - public function addTlsCheckListener(handler:(String, Array<String>)->Bool): Void { - stream.on("tls/check", (data) -> { + public function addTlsCheckListener(handler:(String, Array<String>)->Bool) { + return stream.on("tls/check", (data) -> { return EventValue(handler(data.pem, data.dnsNames)); }); } @@ -1176,8 +1175,11 @@ class Client extends EventEmitter { // TODO: haxe cpp erases enum into int, so using it as a callback arg is hard // could just use int in C bindings, or need to come up with a good strategy // for the wrapper - public function addUserStateListener(handler: (String,String,Null<String>,UserState)->Void):Void { - chatStateHandlers.push(handler); + public function addUserStateListener(handler: (String,String,Null<String>,UserState)->Void):EventHandlerToken { + return this.on("chat-state/update", (data) -> { + handler(data.message.senderId, data.message.chatId, data.message.threadId, data.userState); + return EventHandled; + }); } #end @@ -1187,15 +1189,18 @@ class Client extends EventEmitter { when a ChatMessage is edited, or when a reaction is added @param handler takes two arguments, the ChatMessage and ChatMessageEvent enum describing what happened + @returns token for use with removeEventListener **/ #if cpp // HaxeCBridge doesn't support "secondary" enums yet - public function addChatMessageListener(handler:(ChatMessage, Int)->Void):Void { - chatMessageHandlers.push((m, e) -> handler(m, cast e)); + public function addChatMessageListener(handler:(ChatMessage, Int)->Void) { #else - public function addChatMessageListener(handler:(ChatMessage, ChatMessageEvent)->Void):Void { - chatMessageHandlers.push(handler); + public function addChatMessageListener(handler:(ChatMessage, ChatMessageEvent)->Void):EventHandlerToken { #end + return this.on("chat-state/update", (data) -> { + handler(data.message, data.event); + return EventHandled; + }); } /** @@ -1203,20 +1208,25 @@ class Client extends EventEmitter { Normally you don't want this, but it may be useful if you want to notify on app start. @param handler takes one argument, the ChatMessage + @returns token for use with removeEventListener **/ - public function addSyncMessageListener(handler:(ChatMessage)->Void):Void { - syncMessageHandlers.push(handler); + public function addSyncMessageListener(handler:(ChatMessage)->Void):EventHandlerToken { + return this.on("message/sync", (data) -> { + handler(data); + return EventHandled; + }); } /** Event fired when a Chat's metadata is updated, or when a new Chat is added @param handler takes one argument, an array of Chats that were updated + @returns token for use with removeEventListener **/ private final updateChatBuffer: Map<String, Chat> = []; private var updateChatTimer = null; - public function addChatsUpdatedListener(handler:Array<Chat>->Void):Void { - this.on("chats/update", (data: Array<Chat>) -> { + public function addChatsUpdatedListener(handler:Array<Chat>->Void) { + return this.on("chats/update", (data: Array<Chat>) -> { if (updateChatTimer != null) { updateChatTimer.stop(); } @@ -1237,9 +1247,10 @@ class Client extends EventEmitter { Event fired when a new call comes in @param handler takes one argument, the call Session + @returns token for use with removeEventListener **/ - public function addCallRingListener(handler:(Session)->Void):Void { - this.on("call/ring", (data) -> { + public function addCallRingListener(handler:(Session)->Void) { + return this.on("call/ring", (data) -> { handler(data.session); return EventHandled; }); @@ -1249,9 +1260,10 @@ class Client extends EventEmitter { Event fired when a call is retracted or hung up @param handler takes two arguments, the associated Chat ID and Session ID + @returns token for use with removeEventListener **/ - public function addCallRetractListener(handler:(String,String)->Void):Void { - this.on("call/retract", (data) -> { + public function addCallRetractListener(handler:(String,String)->Void) { + return this.on("call/retract", (data) -> { handler(data.chatId, data.sid); return EventHandled; }); @@ -1261,9 +1273,10 @@ class Client extends EventEmitter { Event fired when an outgoing call starts ringing @param handler takes two arguments, the associated Chat ID and Session ID + @returns token for use with removeEventListener **/ - public function addCallRingingListener(handler:(String,String)->Void):Void { - this.on("call/ringing", (data) -> { + public function addCallRingingListener(handler:(String,String)->Void) { + return this.on("call/ringing", (data) -> { handler(data.chatId, data.sid); return EventHandled; }); @@ -1273,9 +1286,10 @@ class Client extends EventEmitter { Event fired when an existing call changes status (connecting, failed, etc) @param handler takes one argument, the associated Session + @returns token for use with removeEventListener **/ - public function addCallUpdateStatusListener(handler:(InitiatedSession)->Void):Void { - this.on("call/updateStatus", (data) -> { + public function addCallUpdateStatusListener(handler:(InitiatedSession)->Void) { + return this.on("call/updateStatus", (data) -> { handler(data.session); return EventHandled; }); @@ -1287,9 +1301,10 @@ class Client extends EventEmitter { @param handler takes three arguments, the call Session, a boolean indicating if audio is desired, and a boolean indicating if video is desired + @returns token for use with removeEventListener **/ - public function addCallMediaListener(handler:(InitiatedSession,Bool,Bool)->Void):Void { - this.on("call/media", (data) -> { + public function addCallMediaListener(handler:(InitiatedSession,Bool,Bool)->Void) { + return this.on("call/media", (data) -> { handler(data.session, data.audio, data.video); return EventHandled; }); @@ -1300,9 +1315,10 @@ class Client extends EventEmitter { @param handler takes three arguments, the associated Chat ID, the new MediaStreamTrack, and an array of any associated MediaStreams + @returns token for use with removeEventListener **/ - public function addCallTrackListener(handler:(String,MediaStreamTrack,Array<MediaStream>)->Void):Void { - this.on("call/track", (data) -> { + public function addCallTrackListener(handler:(String,MediaStreamTrack,Array<MediaStream>)->Void) { + return this.on("call/track", (data) -> { handler(data.chatId, data.track, data.streams); return EventHandled; }); @@ -1454,9 +1470,7 @@ class Client extends EventEmitter { private function notifyMessageHandlers(message: ChatMessage, event: ChatMessageEvent) { final chat = getChat(message.chatId()); if (chat != null && chat.isBlocked) return; // Don't notify blocked chats - for (handler in chatMessageHandlers) { - handler(message, event); - } + this.trigger("message/new", { message: message, event: event }); } @:allow(borogove) @@ -1464,9 +1478,7 @@ class Client extends EventEmitter { if (message == null || message.versions.length > 1) return; final chat = getChat(message.chatId()); if (chat != null && chat.isBlocked) return; // Don't notify blocked chats - for (handler in syncMessageHandlers) { - handler(message); - } + this.trigger("message/sync", message); } private function rosterGet() { @@ -1645,12 +1657,10 @@ class Client extends EventEmitter { promises.push(persistence.storeMessages(accountId(), chatMessages)); trace("SYNC: MAM page wait for writes"); thenshim.PromiseTools.all(promises).then((results) -> { - if (syncMessageHandlers.length > 0) { - for (messages in results) { - if (messages != null) { - for (message in messages) { - notifySyncMessageHandlers(message); - } + for (messages in results) { + if (messages != null) { + for (message in messages) { + this.trigger("message/sync", message); } } } diff --git a/borogove/EventEmitter.hx b/borogove/EventEmitter.hx index 8cfce0d..355a705 100644 --- a/borogove/EventEmitter.hx +++ b/borogove/EventEmitter.hx @@ -1,38 +1,59 @@ package borogove; -import borogove.EventHandler; +#if cpp +import HaxeCBridge; +#end +enum EventResult { + EventHandled; + EventUnhandled; + EventStop; + EventValue(result:Dynamic); +} + +typedef EventCallback = (Dynamic)->EventResult; + +typedef EventHandlerToken = Int; + +#if cpp +@:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end class EventEmitter { - private var eventHandlers:Map<String,Array<EventHandler>> = []; + private var nextEventHandlerToken = 0; + private var eventHandlers:Map<String,Map<EventHandlerToken, EventCallback>> = []; private function new() { } @:allow(borogove) - private function on(eventName:String, callback:EventCallback):EventHandler { + private function on(eventName:String, callback:EventCallback):EventHandlerToken { var handlers = eventHandlers.get(eventName); if(handlers == null) { handlers = []; eventHandlers.set(eventName, handlers); } - var newHandler = new EventHandler(handlers, callback); - handlers.push(newHandler); - return newHandler; + final token = nextEventHandlerToken++; + handlers[token] = callback; + return token; } - public function once(eventName:String, callback:EventCallback) { - return this.on(eventName, callback).once(); + @:allow(borogove) + private function once(eventName:String, callback:EventCallback):Void { + var token = null; + token = this.on(eventName, (e) -> { + if (token == null) throw "Somehow token was not ready"; + this.removeEventListener(token); + return callback(e); + }); } - public function trigger(eventName:String, eventData:Dynamic):EventResult { + @:allow(borogove) + private function trigger(eventName:String, eventData:Dynamic):EventResult { var handlers = eventHandlers.get(eventName); - if(handlers == null || handlers.length == 0) { - trace('no event handlers for $eventName'); - return EventUnhandled; - } trace("firing event: "+eventName); var handled = false; for (handler in handlers) { - var ret = handler.call(eventData); + var ret = handler(eventData); switch(ret) { case EventHandled: handled = true; case EventUnhandled: continue; @@ -41,4 +62,16 @@ class EventEmitter { } return handled ? EventHandled : EventUnhandled; } + + /** + Remove an event listener of any type, no matter how it was added + or what event it is for. + + @param token the token that was returned when the listener was added + **/ + public function removeEventListener(token:EventHandlerToken) { + for (handlers in eventHandlers) { + handlers.remove(token); + } + } } diff --git a/borogove/EventHandler.hx b/borogove/EventHandler.hx deleted file mode 100644 index 352c806..0000000 --- a/borogove/EventHandler.hx +++ /dev/null @@ -1,41 +0,0 @@ -package borogove; - -enum EventResult { - EventHandled; - EventUnhandled; - EventStop; - EventValue(result:Dynamic); -} - -typedef EventCallback = (Dynamic)->EventResult; - -class EventHandler { - private var handlers:Array<EventHandler> = null; - private var callback:EventCallback = null; - private var onlyOnce:Bool = false; - - public function new(handlers:Array<EventHandler>, callback:EventCallback, ?onlyOnce:Bool) { - this.handlers = handlers; - this.callback = callback; - if(onlyOnce != null) { - this.onlyOnce = onlyOnce; - } - } - - public function call(data:Dynamic):EventResult { - if(onlyOnce) { - this.unsubscribe(); - } - return callback(data); - } - - public function once():EventHandler { - onlyOnce = true; - return this; - } - - public function unsubscribe():Void { - this.handlers.remove(this); - } - -} diff --git a/borogove/MessageSync.hx b/borogove/MessageSync.hx index c0951e9..d1f2830 100644 --- a/borogove/MessageSync.hx +++ b/borogove/MessageSync.hx @@ -68,7 +68,7 @@ class MessageSync { } } var query = new MAMQuery(filter, serviceJID); - var resultHandler = stream.on("message", function (event) { + final eventToken = stream.on("message", function (event) { progress++; var message:Stanza = event.stanza; var from = message.attr.exists("from") ? message.attr.get("from") : client.accountId(); @@ -137,7 +137,7 @@ class MessageSync { return EventHandled; }); query.onFinished(function() { - resultHandler.unsubscribe(); + stream.removeEventListener(eventToken); var result = query.getResult(); if (result == null) { trace("Error from MAM, stopping sync");