| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-03-11 21:27:45 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-03-11 21:27:45 UTC |
| parent | 799f1709a71b458b0ae486e36b3a1c2af1c2a59c |
| HaxeCBridge.hx | +53 | -22 |
| Makefile | +0 | -1 |
| snikket/Chat.hx | +97 | -36 |
| snikket/ChatMessage.hx | +0 | -4 |
| snikket/Client.hx | +16 | -17 |
| snikket/Message.hx | +26 | -8 |
| snikket/jingle/Sessin.hx | +0 | -0 |
| snikket/jingle/Stream.cpp.hx | +0 | -0 |
| snikket/persistence/browser.js | +14 | -33 |
diff --git a/HaxeCBridge.hx b/HaxeCBridge.hx index c1e02c8..6778009 100644 --- a/HaxeCBridge.hx +++ b/HaxeCBridge.hx @@ -182,7 +182,7 @@ class HaxeCBridge { case FFun(fun): var wrapper = { name: field.name + "__fromC", - doc: null, + doc: field.doc, meta: [{name: "HaxeCBridge.wrapper", params: [], pos: Context.currentPos()}], access: field.access.filter(a -> a != AAbstract), kind: null, @@ -191,6 +191,7 @@ class HaxeCBridge { }; var args = []; var passArgs = []; + var outPtr = false; for (arg in fun.args) { switch arg.type { case TFunction(taargs, aret): @@ -214,23 +215,42 @@ class HaxeCBridge { } switch (fun.ret) { case TPath(path) if (path.name == "Array"): - wrapper.ret = TPath({name: "HaxeArray", pack: [], params: path.params.map(t -> convertSecondaryTP(t))}); + outPtr = true; + args.push({name: "outPtr", type: TPath({name: "RawPointer", pack: ["cpp"], params: [TPType(TPath({name: "HaxeArray", pack: [], params: path.params.map(t -> convertSecondaryTP(t))}))]})}); + wrapper.ret = TPath({name: "SizeT", pack: ["cpp"]}); default: } if (args.length > fun.args.length || fun.ret != wrapper.ret || field.access.contains(AAbstract)) { - wrapper.kind = FFun({ret: wrapper.ret, params: fun.params, expr: macro return $i{field.name}($a{passArgs}), args: args}); + if (outPtr) { + wrapper.kind = FFun({ret: wrapper.ret, params: fun.params, expr: macro { final out = $i{field.name}($a{passArgs}); if (outPtr != null) { cpp.Pointer.fromRaw(outPtr).set_ref(out); } return out.length; }, args: args}); + } else { + wrapper.kind = FFun({ret: wrapper.ret, params: fun.params, expr: macro return $i{field.name}($a{passArgs}), args: args}); + } fields.push(wrapper); field.meta.push({name: "HaxeCBridge.noemit", pos: Context.currentPos()}); } case FVar(t, e): - fields.push({ - name: field.name + "__fromC", - doc: null, - meta: [{name: "HaxeCBridge.wrapper", params: [], pos: Context.currentPos()}], - access: field.access, - pos: Context.currentPos(), - kind: FFun({ret: t, params: [], args: [], expr: macro return $i{field.name}}) - }); + switch (t) { + case TPath(path) if (path.name == "Array"): + final ptrT = TPath({name: "RawPointer", pack: ["cpp"], params: [TPType(TPath({name: "HaxeArray", pack: [], params: path.params.map(t -> convertSecondaryTP(t))}))]}); + fields.push({ + name: field.name + "__fromC", + doc: null, + meta: [{name: "HaxeCBridge.wrapper", params: [], pos: Context.currentPos()}], + access: field.access, + pos: Context.currentPos(), + kind: FFun({ret: TPath({name: "SizeT", pack: ["cpp"]}), params: [], args: [{name: "outPtr", type: ptrT}], expr: macro { if (outPtr != null) { cpp.Pointer.fromRaw(outPtr).set_ref($i{field.name}); } return $i{field.name}.length; } }) + }); + default: + fields.push({ + name: field.name + "__fromC", + doc: null, + meta: [{name: "HaxeCBridge.wrapper", params: [], pos: Context.currentPos()}], + access: field.access, + pos: Context.currentPos(), + kind: FFun({ret: t, params: [], args: [], expr: macro return $i{field.name}}) + }); + } default: } } @@ -253,7 +273,7 @@ class HaxeCBridge { case TInst(_.get().name => "Array", tps): [ TPath({name: "HaxeArray", pack: [], params: tps.map((tp) -> convertSecondaryTP(TPType(Context.toComplexType(tp))))}), - TPath({name: "Int", pack: []}) + TPath({name: "SizeT", pack: ["cpp"]}) ]; case TInst(_.get().name => "String", _): [TPath({name: "ConstCharStar", pack: ["cpp"], params: []})]; @@ -454,7 +474,7 @@ class HaxeCBridge { '); } else '') - + 'typedef void (* SnikketPanicCallback) (const char* exceptionInfo);\n' + + 'typedef void (*snikket_panic_callback) (const char *info);\n' + (if (ctx.supportTypeDeclarations.length > 0) ctx.supportTypeDeclarations.map(d -> CPrinter.printDeclaration(d, true)).join(';\n') + ';\n\n'; else '') + (if (ctx.typeDeclarations.length > 0) ctx.typeDeclarations.map(d -> CPrinter.printDeclaration(d, true)).join(';\n') + ';\n'; else '') @@ -472,7 +492,7 @@ class HaxeCBridge { * @param panicCallback a callback to execute if the SDK panics. The SDK will continue processing events after a panic and you may want to stop it after receiving this callback. Use `NULL` for no callback * @returns `NULL` if the thread initializes successfully or a null-terminated C string if an error occurs during initialization */ - $prefix const char *${namespace}_setup(SnikketPanicCallback panicCallback); + $prefix const char *${namespace}_setup(snikket_panic_callback panic_callback); /** * Stops the SDK, blocking until the main thread has completed. Once ended, it cannot be restarted (this is because static variable state will be retained from the last run). @@ -483,9 +503,9 @@ class HaxeCBridge { * * Thread-safety: Can be called safely called on any thread. * - * @param waitOnScheduledEvents If `true`, this function will wait for all events scheduled to execute in the future on the SDK thread to complete. If `false`, immediate pending events will be finished and the SDK stopped without executing events scheduled in the future + * @param wait If `true`, this function will wait for all events scheduled to execute in the future on the SDK thread to complete. If `false`, immediate pending events will be finished and the SDK stopped without executing events scheduled in the future */ - $prefix void ${namespace}_stop(bool waitOnScheduledEvents); + $prefix void ${namespace}_stop(bool wait); ') + indent(1, ctx.supportFunctionDeclarations.map(fn -> CPrinter.printDeclaration(fn, true, prefix)).join(';\n\n') + ';\n\n') @@ -578,7 +598,7 @@ class HaxeCBridge { std::atomic<bool> staticsInitialized = { false }; struct HaxeThreadData { - SnikketPanicCallback haxeExceptionCallback; + snikket_panic_callback haxeExceptionCallback; const char* initExceptionInfo; }; @@ -632,7 +652,7 @@ class HaxeCBridge { threadData->initExceptionInfo = nullptr; // copy out callback - SnikketPanicCallback haxeExceptionCallback = threadData->haxeExceptionCallback; + snikket_panic_callback haxeExceptionCallback = threadData->haxeExceptionCallback; bool firstRun = !HaxeCBridgeInternal::staticsInitialized; @@ -663,7 +683,7 @@ class HaxeCBridge { } HAXE_C_BRIDGE_LINKAGE - const char* ${namespace}_setup(SnikketPanicCallback unhandledExceptionCallback) { + const char* ${namespace}_setup(snikket_panic_callback unhandledExceptionCallback) { HaxeCBridgeInternal::HaxeThreadData threadData; threadData.haxeExceptionCallback = unhandledExceptionCallback == nullptr ? HaxeCBridgeInternal::defaultExceptionHandler : unhandledExceptionCallback; threadData.initExceptionInfo = nullptr; @@ -1050,7 +1070,7 @@ class CPrinter { var name = signature.name; var args = signature.args; var ret = signature.ret; - return '${printType(ret)} $name(${args.map(arg -> '${printType(arg.type, arg.name)}').join(', ')})'.replace(" * ", " *").replace("(* ", "(*").replace("* ", " *"); + return '${printType(ret)} $name(${args.map(arg -> '${printType(arg.type, arg.name)}').join(', ')})'.replace(" * ", " *").replace("(* ", "(*").replace("* ", " *").replace("** ", " **"); } public static function printDoc(doc: String) { @@ -1473,7 +1493,8 @@ class CConverterContext { } function getEnumCType(type: Type, allowNonTrivial: 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))); // `enum ident` is considered non-trivial if (!allowNonTrivial) { @@ -1659,7 +1680,7 @@ class CConverterContext { "auto", "double", "int", "struct", "break", "else", "long", "switch", "case", "enum", "register", "typedef", "char", "extern", "return", "union", "const", "float", "short", "unsigned", "continue", "for", "signed", "void", "default", "goto", "sizeof", "volatile", "do", "if", "static", "while", "size_t", "int64_t", "uint64_t", // HaxeCBridge types - "HaxeObject", "SnikketPanicCallback", + "HaxeObject", "snikket_panic_callback", // hxcpp "Int", "String", "Float", "Dynamic", "Bool", ]; @@ -1748,6 +1769,16 @@ abstract HaxeObject<T>(cpp.RawPointer<cpp.Void>) from cpp.RawPointer<cpp.Void> t } abstract HaxeArray<T>(cpp.RawPointer<cpp.RawPointer<cpp.Void>>) from cpp.RawPointer<cpp.RawPointer<cpp.Void>> to cpp.RawPointer<cpp.RawPointer<cpp.Void>> { + @:from + public static inline function fromArrayString(x: Array<String>): HaxeArray<cpp.ConstCharStar> { + final arr: Array<cpp.SizeT> = cpp.NativeArray.create(x.length); + for (i => el in x) { + final ptr = cpp.ConstCharStar.fromString(el); + arr[i] = untyped untyped __cpp__('reinterpret_cast<size_t>({0})', ptr); + } + return cast HaxeCBridge.retainHaxeArray(arr); + } + @:from public static inline function fromArrayT<T>(x: Array<T>): HaxeArray<HaxeObject<T>> { return HaxeCBridge.retainHaxeArray(x); diff --git a/Makefile b/Makefile index aa8c747..65a7096 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,5 @@ run-nodejs: test.node.js browser.js: haxe browser.hxml echo "var exports = {};" > browser.js - sed -e 's/hxEnums\["snikket.EventResult"\] = {/hxEnums["snikket.EventResult"] = $$hx_exports.snikket.EventResult = {/' < browser.haxe.js | sed -e 's/hxEnums\["snikket.MessageDirection"\] = {/hxEnums["snikket.MessageDirection"] = $$hx_exports.snikket.MessageDirection = {/' | sed -e 's/hxEnums\["snikket.UiState"\] = {/hxEnums["snikket.UiState"] = $$hx_exports.snikket.UiState = {/' | sed -e 's/hxEnums\["snikket.MessageStatus"\] = {/hxEnums["snikket.MessageStatus"] = $$hx_exports.snikket.MessageStatus = {/' >> browser.js cat snikket/persistence/*.js >> browser.js echo "export const { snikket } = exports;" >> browser.js diff --git a/snikket/Chat.hx b/snikket/Chat.hx index 803a554..fbe71c1 100644 --- a/snikket/Chat.hx +++ b/snikket/Chat.hx @@ -17,12 +17,21 @@ using Lambda; import HaxeCBridge; #end -enum UiState { - Pinned; - Open; // or Unspecified - Closed; // Archived +enum abstract UiState(Int) { + var Pinned; + var Open; // or Unspecified + var Closed; // Archived } +#if js +@:expose("UiState") +class UiStateImpl { + static public final Pinned = UiState.Pinned; + static public final Open = UiState.Open; + static public final Closed = UiState.Closed; +} +#end + #if cpp @:build(HaxeCBridge.expose()) #end @@ -35,16 +44,17 @@ abstract class Chat { private var presence:Map<String, Presence> = []; private var trusted:Bool = false; public var chatId(default, null):String; - public var jingleSessions: Map<String, snikket.jingle.Session> = []; + @:allow(snikket) + private var jingleSessions: Map<String, snikket.jingle.Session> = []; private var displayName:String; - @HaxeCBridge.noemit public var uiState = Open; - public var extensions: Stanza; + @:allow(snikket) + private var extensions: Stanza; private var _unreadCount = 0; private var lastMessage: Null<ChatMessage>; @:allow(snikket) - private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState:Dynamic = Open, extensions: Null<Stanza> = null) { + private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, extensions: Null<Stanza> = null) { this.client = client; this.stream = stream; this.persistence = persistence; @@ -54,19 +64,20 @@ abstract class Chat { this.displayName = chatId; } - abstract public function prepareIncomingMessage(message:ChatMessage, stanza:Stanza):ChatMessage; + @:allow(snikket) + abstract private function prepareIncomingMessage(message:ChatMessage, stanza:Stanza):ChatMessage; abstract public function correctMessage(localId:String, message:ChatMessage):Void; abstract public function sendMessage(message:ChatMessage):Void; + abstract public function removeReaction(m:ChatMessage, reaction:String):Void; + abstract public function getMessages(beforeId:Null<String>, beforeTime:Null<String>, handler:(Array<ChatMessage>)->Void):Void; - @HaxeCBridge.noemit abstract public function getParticipants():Array<String>; - @HaxeCBridge.noemit - abstract public function getParticipantDetails(participantId:String, callback:({photoUri:String, displayName:String})->Void):Void; + abstract public function getParticipantDetails(participantId:String, callback:(String, String)->Void):Void; abstract public function bookmark():Void; @@ -111,7 +122,8 @@ abstract class Chat { return _unreadCount; } - public function setUnreadCount(count:Int) { + @:allow(snikket) + private function setUnreadCount(count:Int) { _unreadCount = count; } @@ -119,7 +131,8 @@ abstract class Chat { return lastMessage?.text ?? ""; } - public function setLastMessage(message:Null<ChatMessage>) { + @:allow(snikket) + private function setLastMessage(message:Null<ChatMessage>) { lastMessage = message; } @@ -131,11 +144,13 @@ abstract class Chat { return this.displayName; } - public function setPresence(resource:String, presence:Presence) { + @:allow(snikket) + private function setPresence(resource:String, presence:Presence) { this.presence.set(resource, presence); } - public function setCaps(resource:String, caps:Caps) { + @:allow(snikket) + private function setCaps(resource:String, caps:Caps) { final presence = presence.get(resource); if (presence != null) { presence.caps = caps; @@ -145,11 +160,13 @@ abstract class Chat { } } - public function removePresence(resource:String) { + @:allow(snikket) + private function removePresence(resource:String) { presence.remove(resource); } - public function getCaps():KeyValueIterator<String, Caps> { + @:allow(snikket) + private function getCaps():KeyValueIterator<String, Caps> { final iter = presence.keyValueIterator(); return { hasNext: iter.hasNext, @@ -160,7 +177,8 @@ abstract class Chat { }; } - public function getResourceCaps(resource:String):Caps { + @:allow(snikket) + private function getResourceCaps(resource:String):Caps { return presence[resource]?.caps ?? new Caps("", [], []); } @@ -177,7 +195,8 @@ abstract class Chat { return this.trusted; } - public function livePresence() { + @:allow(snikket) + private function livePresence() { return false; } @@ -239,8 +258,7 @@ abstract class Chat { return null; } - @HaxeCBridge.noemit - public function videoTracks() { + public function videoTracks(): Array<MediaStreamTrack> { return jingleSessions.flatMap((session) -> session.videoTracks()); } @@ -275,9 +293,9 @@ class DirectChat extends Chat { } @HaxeCBridge.noemit // on superclass as abstract - public function getParticipantDetails(participantId:String, callback:({photoUri:String, displayName:String})->Void) { + public function getParticipantDetails(participantId:String, callback:(String, String)->Void) { final chat = client.getDirectChat(participantId); - chat.getPhoto((photoUri) -> callback({ photoUri: photoUri, displayName: chat.getDisplayName() })); + chat.getPhoto((photoUri) -> callback(photoUri, chat.getDisplayName())); } @HaxeCBridge.noemit // on superclass as abstract @@ -309,7 +327,8 @@ class DirectChat extends Chat { }); } - public function prepareIncomingMessage(message:ChatMessage, stanza:Stanza) { + @:allow(snikket) + private function prepareIncomingMessage(message:ChatMessage, stanza:Stanza) { message.syncPoint = true; // TODO: if client is done initial MAM. right now it always is return message; } @@ -373,6 +392,7 @@ class DirectChat extends Chat { } } + @HaxeCBridge.noemit // on superclass as abstract public function removeReaction(m:ChatMessage, reaction:String) { // NOTE: doing it this way means no fallback behaviour final reactions = []; @@ -390,10 +410,12 @@ class DirectChat extends Chat { }); } + @HaxeCBridge.noemit // on superclass as abstract public function lastMessageId() { return lastMessage?.localId ?? lastMessage?.serverId; } + @HaxeCBridge.noemit // on superclass as abstract public function markReadUpTo(message: ChatMessage) { if (readUpTo() == message.localId || readUpTo() == message.serverId) return; final upTo = message.localId ?? message.serverId; @@ -420,6 +442,7 @@ class DirectChat extends Chat { client.trigger("chats/update", [this]); } + @HaxeCBridge.noemit // on superclass as abstract public function bookmark() { stream.sendIq( new Stanza("iq", { type: "set" }) @@ -434,6 +457,7 @@ class DirectChat extends Chat { ); } + @HaxeCBridge.noemit // on superclass as abstract public function close() { // Should this remove from roster? uiState = Closed; @@ -443,11 +467,16 @@ class DirectChat extends Chat { } @:expose +#if cpp +@:build(HaxeCBridge.expose()) +#end class Channel extends Chat { - public var disco: Caps = new Caps("", [], ["http://jabber.org/protocol/muc"]); + @:allow(snikket) + private var disco: Caps = new Caps("", [], ["http://jabber.org/protocol/muc"]); private var inSync = true; - public function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, extensions = null, ?disco: Caps) { + @:allow(snikket) + private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, extensions = null, ?disco: Caps) { super(client, stream, persistence, chatId, uiState, extensions); if (disco != null) this.disco = disco; } @@ -572,7 +601,8 @@ class Channel extends Chat { return lastMessage.sender.resource + ": " + super.preview(); } - override public function livePresence() { + @:allow(snikket) + override private function livePresence() { for (nick => p in presence) { for (status in p?.mucUser?.allTags("status") ?? []) { if (status.attr.get("code") == "110") { @@ -598,33 +628,32 @@ class Channel extends Chat { return JID.parse(chatId).withResource(nickInUse()); } + @HaxeCBridge.noemit // on superclass as abstract public function getParticipants() { final jid = JID.parse(chatId); return { iterator: () -> presence.keys() }.map((resource) -> new JID(jid.node, jid.domain, resource).asString()); } - public function getParticipantDetails(participantId:String, callback:({photoUri:String, displayName:String})->Void) { + @HaxeCBridge.noemit // on superclass as abstract + public function getParticipantDetails(participantId:String, callback:(String, String)->Void) { if (participantId == getFullJid().asString()) { client.getDirectChat(client.accountId(), false).getPhoto((photoUri) -> { - callback({ photoUri: photoUri, displayName: client.displayName() }); + callback(photoUri, client.displayName()); }); } else { final nick = JID.parse(participantId).resource; final photoUri = Color.defaultPhoto(participantId, nick == null ? " " : nick.charAt(0)); - callback({ photoUri: photoUri, displayName: nick }); + callback(photoUri, nick); } } + @HaxeCBridge.noemit // on superclass as abstract public function getMessages(beforeId:Null<String>, beforeTime:Null<String>, handler:(Array<ChatMessage>)->Void):Void { - trace("1"); return; persistence.getMessages(client.accountId(), chatId, beforeId, beforeTime, (messages) -> { - trace("2"); if (messages.length > 0) { - trace("3"); handler(messages); } else { - trace("4"); var filter:MAMQueryParams = {}; if (beforeId != null) filter.page = { before: beforeId }; var sync = new MessageSync(this.client, this.stream, filter, chatId); @@ -649,7 +678,8 @@ class Channel extends Chat { }); } - public function prepareIncomingMessage(message:ChatMessage, stanza:Stanza) { + @:allow(snikket) + private function prepareIncomingMessage(message:ChatMessage, stanza:Stanza) { message.syncPoint = inSync; message.sender = JID.parse(stanza.attr.get("from")); // MUC always needs full JIDs if (message.senderId() == getFullJid().asString()) { @@ -671,6 +701,7 @@ class Channel extends Chat { return message; } + @HaxeCBridge.noemit // on superclass as abstract public function correctMessage(localId:String, message:ChatMessage) { final toSend = message.clone(); message = prepareOutgoingMessage(message); @@ -687,6 +718,7 @@ class Channel extends Chat { }); } + @HaxeCBridge.noemit // on superclass as abstract public function sendMessage(message:ChatMessage):Void { client.chatActivity(this); message = prepareOutgoingMessage(message); @@ -714,6 +746,7 @@ class Channel extends Chat { } } + @HaxeCBridge.noemit // on superclass as abstract public function removeReaction(m:ChatMessage, reaction:String) { // NOTE: doing it this way means no fallback behaviour final reactions = []; @@ -729,10 +762,12 @@ class Channel extends Chat { }); } + @HaxeCBridge.noemit // on superclass as abstract public function lastMessageId() { return lastMessage?.serverId; } + @HaxeCBridge.noemit // on superclass as abstract public function markReadUpTo(message: ChatMessage) { if (readUpTo() == message.serverId) return; final upTo = message.serverId; @@ -757,6 +792,7 @@ class Channel extends Chat { client.trigger("chats/update", [this]); } + @HaxeCBridge.noemit // on superclass as abstract public function bookmark() { stream.sendIq( new Stanza("iq", { type: "set" }) @@ -807,6 +843,7 @@ class Channel extends Chat { ); } + @HaxeCBridge.noemit // on superclass as abstract public function close() { uiState = Closed; persistence.storeChat(client.accountId(), this); @@ -816,6 +853,30 @@ class Channel extends Chat { } } +@:expose +#if cpp +@:build(HaxeCBridge.expose()) +#end +class AvailableChat { + public final chatId: String; + public final displayName: Null<String>; + public final note: String; + @:allow(snikket) + private final caps: Caps; + + public function isChannel() { + return caps.isChannel(chatId); + } + + @:allow(snikket) + private function new(chatId: String, displayName: Null<String>, note: String, caps: Caps) { + this.chatId = chatId; + this.displayName = displayName; + this.note = note; + this.caps = caps; + } +} + @:expose class SerializedChat { public final chatId:String; diff --git a/snikket/ChatMessage.hx b/snikket/ChatMessage.hx index fbea4dd..ac558cf 100644 --- a/snikket/ChatMessage.hx +++ b/snikket/ChatMessage.hx @@ -61,7 +61,6 @@ class ChatMessage { public var replyToMessage: Null<ChatMessage> = null; public var threadId: Null<String> = null; - @HaxeCBridge.noemit public var attachments: Array<ChatAttachment> = []; public var reactions: Map<String, Array<String>> = []; @@ -69,11 +68,8 @@ class ChatMessage { public var lang: Null<String> = null; public var isGroupchat: Bool = false; // Only really useful for distinguishing whispers - @HaxeCBridge.noemit public var direction: MessageDirection = MessageReceived; - @HaxeCBridge.noemit public var status: MessageStatus = MessagePending; - @HaxeCBridge.noemit public var versions: Array<ChatMessage> = []; @:allow(snikket) private var payloads: Array<Stanza> = []; diff --git a/snikket/Client.hx b/snikket/Client.hx index a92e579..09a9ec3 100644 --- a/snikket/Client.hx +++ b/snikket/Client.hx @@ -379,7 +379,7 @@ class Client extends EventEmitter { /** Get the account ID for this Client - @return account id + @returns account id **/ public function accountId() { return jid.asBare().asString(); @@ -600,19 +600,19 @@ class Client extends EventEmitter { } #end - /* Return array of chats, sorted by last activity */ + /** + @returns array of chats, sorted by last activity + */ public function getChats():Array<Chat> { return chats.filter((chat) -> chat.uiState != Closed); } - // We can ask for caps here because presumably they looked this up - // via findAvailableChats - public function startChat(chatId:String, displayName:Null<String>, caps:Caps):Chat { - final existingChat = getChat(chatId); + public function startChat(availableChat: AvailableChat):Chat { + final existingChat = getChat(availableChat.chatId); if (existingChat != null) { final channel = Std.downcast(existingChat, Channel); - if (channel == null && caps.isChannel(chatId)) { - chats = chats.filter((chat) -> chat.chatId == chatId); + if (channel == null && availableChat.isChannel()) { + chats = chats.filter((chat) -> chat.chatId != availableChat.chatId); } else { if (existingChat.uiState == Closed) existingChat.uiState = Open; channel?.selfPing(); @@ -621,15 +621,15 @@ class Client extends EventEmitter { } } - final chat = if (caps.isChannel(chatId)) { - final channel = new Channel(this, this.stream, this.persistence, chatId, Open, null, caps); + final chat = if (availableChat.isChannel()) { + final channel = new Channel(this, this.stream, this.persistence, availableChat.chatId, Open, null, availableChat.caps); chats.unshift(channel); channel.selfPing(false); channel; } else { - getDirectChat(chatId, false); + getDirectChat(availableChat.chatId, false); } - if (displayName != null) chat.setDisplayName(displayName); + if (availableChat.displayName != null) chat.setDisplayName(availableChat.displayName); persistence.storeChat(accountId(), chat); this.trigger("chats/update", [chat]); return chat; @@ -652,8 +652,7 @@ class Client extends EventEmitter { return chat; } - @HaxeCBridge.noemit - public function findAvailableChats(q:String, callback:(String, Array<{ chatId: String, fn: String, note: String, caps: Caps }>) -> Void) { + public function findAvailableChats(q:String, callback:(String, Array<AvailableChat>) -> Void) { var results = []; final query = StringTools.trim(q); final jid = JID.parse(query); @@ -664,14 +663,14 @@ class Client extends EventEmitter { if (resultCaps == null) { final err = discoGet.responseStanza?.getChild("error")?.getChild(null, "urn:ietf:params:xml:ns:xmpp-stanzas"); if (err == null || err?.name == "service-unavailable" || err?.name == "feature-not-implemented") { - results.push({ chatId: jid.asString(), fn: query, note: jid.asString(), caps: new Caps("", [], []) }); + results.push(new AvailableChat(jid.asString(), query, jid.asString(), new Caps("", [], []))); } } else { persistence.storeCaps(resultCaps); final identity = resultCaps.identities[0]; - final fn = identity?.name ?? query; + final displayName = identity?.name ?? query; final note = jid.asString() + (identity == null ? "" : " (" + identity.type + ")"); - results.push({ chatId: jid.asString(), fn: fn, note: note, caps: resultCaps }); + results.push(new AvailableChat(jid.asString(), displayName, note, resultCaps)); } callback(q, results); }); diff --git a/snikket/Message.hx b/snikket/Message.hx index 863b2d2..bee5488 100644 --- a/snikket/Message.hx +++ b/snikket/Message.hx @@ -2,17 +2,35 @@ package snikket; using Lambda; -enum MessageDirection { - MessageReceived; - MessageSent; +enum abstract MessageDirection(Int) { + var MessageReceived; + var MessageSent; } -enum MessageStatus { - MessagePending; // Message is waiting in client for sending - MessageDeliveredToServer; // Server acknowledged receipt of the message - MessageDeliveredToDevice; //The message has been delivered to at least one client device - MessageFailedToSend; // There was an error sending this message +#if js +@:expose("MessageDirection") +class MessageDirectionImpl { + static public final MessageReceived = MessageDirection.MessageReceived; + static public final MessageSent = MessageDirection.MessageSent; } +#end + +enum abstract MessageStatus(Int) { + var MessagePending; // Message is waiting in client for sending + var MessageDeliveredToServer; // Server acknowledged receipt of the message + var MessageDeliveredToDevice; //The message has been delivered to at least one client device + var MessageFailedToSend; // There was an error sending this message +} + +#if js +@:expose("MessageStatus") +class MessageStatusImpl { + static public final MessagePending = MessageStatus.MessagePending; + static public final MessageDeliveredToServer = MessageStatus.MessageDeliveredToServer; + static public final MessageDeliveredToDevice = MessageStatus.MessageDeliveredToDevice; + static public final MessageFailedToSend = MessageStatus.MessageFailedToSend; +} +#end enum MessageStanza { ErrorMessageStanza(stanza: Stanza); diff --git a/snikket/jingle/Sessin.hx b/snikket/jingle/Sessin.hx new file mode 100644 index 0000000..e69de29 diff --git a/snikket/jingle/Stream.cpp.hx b/snikket/jingle/Stream.cpp.hx new file mode 100644 index 0000000..e69de29 diff --git a/snikket/persistence/browser.js b/snikket/persistence/browser.js index 6b8ba16..e800db3 100644 --- a/snikket/persistence/browser.js +++ b/snikket/persistence/browser.js @@ -1,7 +1,7 @@ // This example persistence driver is written in JavaScript // so that SDK users can easily see how to write their own -exports.xmpp.persistence = { +exports.snikket.persistence = { browser: (dbname) => { var db = null; function openDb(version) { @@ -62,17 +62,17 @@ exports.xmpp.persistence = { const store = tx.objectStore("messages"); let replyToMessage = value.replyToMessage && await hydrateMessage((await promisifyRequest(store.openCursor(IDBKeyRange.only(value.replyToMessage))))?.value); - const message = new xmpp.ChatMessage(); + const message = new snikket.ChatMessage(); message.localId = value.localId ? value.localId : null; message.serverId = value.serverId ? value.serverId : null; message.serverIdBy = value.serverIdBy ? value.serverIdBy : null; message.syncPoint = !!value.syncPoint; message.timestamp = value.timestamp && value.timestamp.toISOString(); - message.to = value.to && xmpp.JID.parse(value.to); - message.from = value.from && xmpp.JID.parse(value.from); - message.sender = value.sender && xmpp.JID.parse(value.sender); - message.recipients = value.recipients.map((r) => xmpp.JID.parse(r)); - message.replyTo = value.replyTo.map((r) => xmpp.JID.parse(r)); + message.to = value.to && snikket.JID.parse(value.to); + message.from = value.from && snikket.JID.parse(value.from); + message.sender = value.sender && snikket.JID.parse(value.sender); + message.recipients = value.recipients.map((r) => snikket.JID.parse(r)); + message.replyTo = value.replyTo.map((r) => snikket.JID.parse(r)); message.replyToMessage = replyToMessage; message.threadId = value.threadId; message.attachments = value.attachments; @@ -80,25 +80,8 @@ exports.xmpp.persistence = { message.text = value.text; message.lang = value.lang; message.isGroupchat = value.isGroupchat || value.groupchat; - message.direction = value.direction == "MessageReceived" ? xmpp.MessageDirection.MessageReceived : xmpp.MessageDirection.MessageSent; - switch (value.status) { - case "MessagePending": - message.status = xmpp.MessageStatus.MessagePending; - break; - case "MessageDeliveredToServer": - message.status = xmpp.MessageStatus.MessageDeliveredToServer; - break; - case "MessageDeliveredToDevice": - message.status = xmpp.MessageStatus.MessageDeliveredToDevice; - break; - case "MessageFailedToSend": - message.status = xmpp.MessageStatus.MessageFailedToSend; - break; - default: - message.status = message.serverId ? xmpp.MessageStatus.MessageDeliveredToServer : xmpp.MessageStatus.MessagePending; - } message.versions = await Promise.all((value.versions || []).map(hydrateMessage)); - message.payloads = (value.payloads || []).map(xmpp.Stanza.parse); + message.payloads = (value.payloads || []).map(snikket.Stanza.parse); return message; } @@ -118,8 +101,6 @@ exports.xmpp.persistence = { replyTo: message.replyTo.map((r) => r.asString()), timestamp: new Date(message.timestamp), replyToMessage: message.replyToMessage && [account, message.replyToMessage.serverId || "", message.replyToMessage.serverIdBy || "", message.replyToMessage.localId || ""], - direction: message.direction.toString(), - status: message.status.toString(), versions: message.versions.map((m) => serializeMessage(account, m)), payloads: message.payloads.map((p) => p.toString()), } @@ -199,10 +180,10 @@ exports.xmpp.persistence = { avatarSha1: chat.avatarSha1, presence: new Map([...chat.presence.entries()].map(([k, p]) => [k, { caps: p.caps?.ver(), mucUser: p.mucUser?.toString() }])), displayName: chat.displayName, - uiState: chat.uiState?.toString(), + uiState: chat.uiState, extensions: chat.extensions?.toString(), disco: chat.disco, - class: chat instanceof xmpp.DirectChat ? "DirectChat" : (chat instanceof xmpp.Channel ? "Channel" : "Chat") + class: chat instanceof snikket.DirectChat ? "DirectChat" : (chat instanceof snikket.Channel ? "Channel" : "Chat") }); }, @@ -212,12 +193,12 @@ exports.xmpp.persistence = { const store = tx.objectStore("chats"); const range = IDBKeyRange.bound([account], [account, []]); const result = await promisifyRequest(store.getAll(range)); - return await Promise.all(result.map(async (r) => new xmpp.SerializedChat( + return await Promise.all(result.map(async (r) => new snikket.SerializedChat( r.chatId, r.trusted, r.avatarSha1, new Map(await Promise.all((r.presence instanceof Map ? [...r.presence.entries()] : Object.entries(r.presence)).map( - async ([k, p]) => [k, new xmpp.Presence(p.caps && await new Promise((resolve) => this.getCaps(p.caps, resolve)), p.mucUser && xmpp.Stanza.parse(p.mucUser))] + async ([k, p]) => [k, new snikket.Presence(p.caps && await new Promise((resolve) => this.getCaps(p.caps, resolve)), p.mucUser && snikket.Stanza.parse(p.mucUser))] ))), r.displayName, r.uiState, @@ -363,7 +344,7 @@ exports.xmpp.persistence = { const store = tx.objectStore("messages"); promisifyRequest(store.index("localId").openCursor(IDBKeyRange.bound([account, localId], [account, localId, []]))).then((result) => { if (result?.value && result.value.direction === "MessageSent" && result.value.status !== "MessageDeliveredToDevice") { - const newStatus = { ...result.value, status: status.toString() }; + const newStatus = { ...result.value, status: status }; result.update(newStatus); hydrateMessage(newStatus).then(callback); } @@ -450,7 +431,7 @@ exports.xmpp.persistence = { const store = tx.objectStore("keyvaluepairs"); const raw = await promisifyRequest(store.get("caps:" + ver)); if (raw) { - return (new xmpp.Caps(raw.node, raw.identities.map((identity) => new xmpp.Identity(identity.category, identity.type, identity.name)), raw.features)); + return (new snikket.Caps(raw.node, raw.identities.map((identity) => new snikket.Identity(identity.category, identity.type, identity.name)), raw.features)); } return null;