Skip to content

Commit

Permalink
Initial handler to subscribe to user state
Browse files Browse the repository at this point in the history
Is the user typing? Paused typing? Active in the chat but not typing?
Etc.

This API might also make sense to be used to presence changes generally
as well, in which case there would be no chatId/threadId attached?
  • Loading branch information
singpolyma committed Sep 24, 2024
1 parent e8a4f64 commit 9d708b4
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 15 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ npm/snikket.js:
sed -i 's/snikket\.UiState/enums.UiState/g' npm/snikket.d.ts
sed -i 's/snikket\.MessageStatus/enums.MessageStatus/g' npm/snikket.d.ts
sed -i 's/snikket\.MessageDirection/enums.MessageDirection/g' npm/snikket.d.ts
sed -i 's/snikket\.UserState/enums.UserState/g' npm/snikket.d.ts
sed -i '1ivar exports = {};' npm/snikket.js
echo "export const snikket = exports.snikket;" >> npm/snikket.js

Expand Down
1 change: 1 addition & 0 deletions npm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export import jingle = snikket.jingle;
export import UiState = enums.UiState;
export import MessageStatus = enums.MessageStatus;
export import MessageDirection = enums.MessageDirection;
export import UserState = enums.UserState;

export namespace persistence {
export import browser = browserp;
Expand Down
12 changes: 10 additions & 2 deletions snikket/Chat.hx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ enum abstract UiState(Int) {
var Closed; // Archived
}

enum abstract UserState(Int) {
var Gone;
var Inactive;
var Active;
var Composing;
var Paused;
}

#if cpp
@:build(HaxeCBridge.expose())
@:build(HaxeSwiftBridge.expose())
Expand Down Expand Up @@ -611,7 +619,7 @@ class DirectChat extends Chat {
if (typingTimer != null) typingTimer.stop();
client.chatActivity(this);
message = prepareOutgoingMessage(message);
final fromStanza = Message.fromStanza(message.asStanza(), client.jid);
final fromStanza = Message.fromStanza(message.asStanza(), client.jid).parsed;
switch (fromStanza) {
case ChatMessageStanza(_):
persistence.storeMessage(client.accountId(), message, (stored) -> {
Expand Down Expand Up @@ -1021,7 +1029,7 @@ class Channel extends Chat {
final stanza = message.asStanza();
// Fake from as it will look on reflection for storage purposes
stanza.attr.set("from", getFullJid().asString());
final fromStanza = Message.fromStanza(stanza, client.jid);
final fromStanza = Message.fromStanza(stanza, client.jid).parsed;
stanza.attr.set("from", client.jid.asString());
switch (fromStanza) {
case ChatMessageStanza(_):
Expand Down
2 changes: 1 addition & 1 deletion snikket/ChatMessage.hx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class ChatMessage {

@:allow(snikket)
private static function fromStanza(stanza:Stanza, localJid:JID):Null<ChatMessage> {
switch Message.fromStanza(stanza, localJid) {
switch Message.fromStanza(stanza, localJid).parsed {
case ChatMessageStanza(message):
return message;
default:
Expand Down
30 changes: 29 additions & 1 deletion snikket/Client.hx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Client extends EventEmitter {
public var sendAvailable(null, default): Bool = true;
private var stream:GenericStream;
private var chatMessageHandlers: Array<(ChatMessage)->Void> = [];
private var chatStateHandlers: Array<(String,String,Null<String>,UserState)->Void> = [];
@:allow(snikket)
private var jid(default,null):JID;
private var chats: Array<Chat> = [];
Expand Down Expand Up @@ -196,7 +197,8 @@ class Client extends EventEmitter {
}
}

switch (Message.fromStanza(stanza, this.jid)) {
final message = Message.fromStanza(stanza, this.jid);
switch (message.parsed) {
case ChatMessageStanza(chatMessage):
var chat = getChat(chatMessage.chatId());
if (chat == null && stanza.attr.get("type") != "groupchat") chat = getDirectChat(chatMessage.chatId());
Expand All @@ -222,6 +224,23 @@ class Client extends EventEmitter {
// ignore
}

if (stanza.attr.get("type") != "error") {
final chatState = stanza.getChild(null, "http://jabber.org/protocol/chatstates");
final userState = switch (chatState?.name) {
case "active": UserState.Active;
case "inactive": UserState.Inactive;
case "gone": UserState.Gone;
case "composing": UserState.Composing;
case "paused": UserState.Paused;
default: null;
};
if (userState != null) {
for (handler in chatStateHandlers) {
handler(message.senderId, message.chatId, message.threadId, userState);
}
}
}

final pubsubEvent = PubsubEvent.fromStanza(stanza);
if (pubsubEvent != null && pubsubEvent.getFrom() != null && pubsubEvent.getNode() == "urn:xmpp:avatar:metadata" && pubsubEvent.getItems().length > 0) {
final item = pubsubEvent.getItems()[0];
Expand Down Expand Up @@ -857,6 +876,15 @@ class Client extends EventEmitter {
});
}

#if !cpp
// 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);
}
#end

/**
Event fired when a new ChatMessage comes in on any Chat
Also fires when status of a ChatMessage changes,
Expand Down
33 changes: 23 additions & 10 deletions snikket/Message.hx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,22 @@ enum MessageStanza {

@:nullSafety(Strict)
class Message {
public static function fromStanza(stanza:Stanza, localJid:JID, ?inputTimestamp: String):MessageStanza {
if (stanza.attr.get("type") == "error") return ErrorMessageStanza(stanza);
public final chatId: String;
public final senderId: String;
public final threadId: Null<String>;
public final parsed: MessageStanza;

private function new(chatId: String, senderId: String, threadId: Null<String>, parsed: MessageStanza) {
this.chatId = chatId;
this.senderId = senderId;
this.threadId = threadId;
this.parsed = parsed;
}

public static function fromStanza(stanza:Stanza, localJid:JID, ?inputTimestamp: String):Message {
final fromAttr = stanza.attr.get("from");
final from = fromAttr == null ? localJid.domain : fromAttr;
if (stanza.attr.get("type") == "error") return new Message(from, from, null, ErrorMessageStanza(stanza));

var msg = new ChatMessage();
final timestamp = stanza.findText("{urn:xmpp:delay}delay@stamp") ?? inputTimestamp ?? Date.format(std.Date.now());
Expand All @@ -35,8 +49,7 @@ class Message {
if (msg.text != null && (msg.lang == null || msg.lang == "")) {
msg.lang = stanza.getChild("body")?.attr.get("xml:lang");
}
final from = stanza.attr.get("from");
msg.from = from == null ? null : JID.parse(from);
msg.from = JID.parse(from);
msg.isGroupchat = stanza.attr.get("type") == "groupchat";
msg.sender = stanza.attr.get("type") == "groupchat" ? msg.from : msg.from?.asBare();
final localJidBare = localJid.asBare();
Expand Down Expand Up @@ -97,7 +110,7 @@ class Message {
replyTo.clear();
} else if (jid == null) {
trace("No support for addressing to non-jid", address);
return UnknownMessageStanza(stanza);
return new Message(msg.chatId(), msg.senderId(), msg.threadId, UnknownMessageStanza(stanza));
} else if (address.attr.get("type") == "to" || address.attr.get("type") == "cc") {
recipients[JID.parse(jid).asBare().asString()] = true;
if (!anyExtendedReplyTo) replyTo[JID.parse(jid).asString()] = true; // reply all
Expand All @@ -124,7 +137,7 @@ class Message {
final msgFrom = msg.from;
if (msg.direction == MessageReceived && msgFrom != null && msg.replyTo.find((r) -> r.asBare().equals(msgFrom.asBare())) == null) {
trace("Don't know what chat message without from in replyTo belongs in", stanza);
return UnknownMessageStanza(stanza);
return new Message(msg.chatId(), msg.senderId(), msg.threadId, UnknownMessageStanza(stanza));
}

final reactionsEl = stanza.getChild("reactions", "urn:xmpp:reactions:0");
Expand All @@ -133,15 +146,15 @@ class Message {
final reactions = reactionsEl.allTags("reaction").map((r) -> r.getText());
final reactionId = reactionsEl.attr.get("id");
if (reactionId != null) {
return ReactionUpdateStanza(new ReactionUpdate(
return new Message(msg.chatId(), msg.senderId(), msg.threadId, ReactionUpdateStanza(new ReactionUpdate(
stanza.attr.get("id") ?? ID.long(),
stanza.attr.get("type") == "groupchat" ? reactionId : null,
stanza.attr.get("type") != "groupchat" ? reactionId : null,
msg.chatId(),
timestamp,
msg.senderId(),
reactions
));
)));
}
}

Expand All @@ -156,7 +169,7 @@ class Message {
msg.attachSims(sims);
}

if (msg.text == null && msg.attachments.length < 1) return UnknownMessageStanza(stanza);
if (msg.text == null && msg.attachments.length < 1) return new Message(msg.chatId(), msg.senderId(), msg.threadId, UnknownMessageStanza(stanza));

for (fallback in stanza.allTags("fallback", "urn:xmpp:fallback:0")) {
msg.payloads.push(fallback);
Expand Down Expand Up @@ -192,6 +205,6 @@ class Message {
Reflect.setField(msg, "localId", replaceId);
}

return ChatMessageStanza(msg);
return new Message(msg.chatId(), msg.senderId(), msg.threadId, ChatMessageStanza(msg));
}
}
2 changes: 1 addition & 1 deletion snikket/MessageSync.hx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class MessageSync {
jmi.set(jmiChildren[0].attr.get("id"), originalMessage);
}

var msg = Message.fromStanza(originalMessage, client.jid, timestamp);
final msg = Message.fromStanza(originalMessage, client.jid, timestamp).parsed;

switch (msg) {
case ChatMessageStanza(chatMessage):
Expand Down

0 comments on commit 9d708b4

Please sign in to comment.