| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-03-25 15:24:35 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2024-03-25 15:24:35 UTC |
| parent | 3086192d01d304cccc3c4cb4caff2d60291d69d4 |
| HaxeSwiftBridge.hx | +626 | -0 |
| snikket/Chat.hx | +4 | -0 |
| snikket/ChatMessage.hx | +14 | -3 |
| snikket/Client.hx | +2 | -1 |
| snikket/Persistence.hx | +5 | -0 |
| snikket/jingle/PeerConnection.cpp.hx | +20 | -2 |
| snikket/jingle/Session.hx | +3 | -0 |
| snikket/persistence/Sqlite.hx | +3 | -0 |
diff --git a/HaxeSwiftBridge.hx b/HaxeSwiftBridge.hx new file mode 100644 index 0000000..f103bf3 --- /dev/null +++ b/HaxeSwiftBridge.hx @@ -0,0 +1,626 @@ +#if (haxe_ver < 4.0) #error "Haxe 4.0 required" #end + +#if macro + + // fast path for when code gen isn't required + // disable this to get auto-complete when editing this file + #if (display || display_details || !sys || cppia) + +class HaxeSwiftBridge { + public static function expose(?namespace: String) + return haxe.macro.Context.getBuildFields(); + @:noCompletion + static macro function runUserMain() + return macro null; +} + + #else + +import HaxeCBridge.CodeTools.*; +import haxe.ds.ReadOnlyArray; +import haxe.io.Path; +import haxe.macro.Compiler; +import haxe.macro.ComplexTypeTools; +import haxe.macro.Context; +import haxe.macro.Expr; +import haxe.macro.ExprTools; +import haxe.macro.PositionTools; +import haxe.macro.Printer; +import haxe.macro.Type; +import haxe.macro.TypeTools; +import haxe.macro.TypedExprTools; +import sys.FileSystem; +import sys.io.File; + +using Lambda; +using StringTools; + +class HaxeSwiftBridge { + + static final noOutput = Sys.args().has('--no-output'); + static final printer = new Printer(); + + static var firstRun = true; + + static var libName: Null<String> = getLibNameFromHaxeArgs(); // null if no libName determined from args + + static final compilerOutputDir = Compiler.getOutput(); + // paths relative to the compiler output directory + static final implementationPath = Path.join([hx.strings.Strings.toUpperCamel(libName) + '.swift']); + + static final queuedClasses = new Array<{ + cls: Ref<ClassType>, + namespace: String, + }>(); + + static final knownEnums: Map<String,String> = []; + + static public function expose(?namespace: String) { + var clsRef = Context.getLocalClass(); + var cls = clsRef.get(); + var fields = Context.getBuildFields(); + + if (libName == null) { + // if we cannot determine a libName from --main or -D, we use the first exposed class + libName = if (namespace != null) { + namespace; + } else { + cls.name; + } + } + + queuedClasses.push({ + cls: clsRef, + namespace: namespace + }); + + // add @:keep + cls.meta.add(':keep', [], Context.currentPos()); + + if (firstRun) { + var HaxeSwiftBridgeType = Context.resolveType(macro :HaxeSwiftBridge, Context.currentPos()); + switch HaxeSwiftBridgeType { + case TInst(_.get().meta => meta, params): + if (!meta.has(':buildXml')) { + meta.add(':buildXml', [ + macro $v{code(' + <!-- HaxeSwiftBridge --> + <files id="haxe"> + <depend name="$implementationPath"/> + </files> + ')} + ], Context.currentPos()); + } + default: throw 'Internal error'; + } + + Context.onAfterTyping(_ -> { + var implementation = generateImplementation(); + + function saveFile(path: String, content: String) { + var directory = Path.directory(path); + if (!FileSystem.exists(directory)) { + FileSystem.createDirectory(directory); + } + // only save if there's a difference (save C++ compilation by not changing the file if not needed) + if (FileSystem.exists(path)) { + if (content == sys.io.File.getContent(path)) { + return; + } + } + sys.io.File.saveContent(path, content); + } + + if (!noOutput) { + saveFile(Path.join([compilerOutputDir, implementationPath]), implementation); + } + }); + + firstRun = false; + } + + return fields; + } + + static function getHxcppNativeName(t: BaseType) { + var nativeMeta = t.meta.extract(':native')[0]; + var nativeMetaValue = nativeMeta != null ? ExprTools.getValue(nativeMeta.params[0]) : null; + var nativeName = (nativeMetaValue != null ? nativeMetaValue : t.pack.concat([t.name]).join('.')); + return nativeName; + } + + static function getSwiftType(type, arg = false) { + return switch type { + case TInst(_.get().name => "String", params): + return "String"; + case TInst(_.get().name => "Array", [param]): + return "Array<" + getSwiftType(param, arg) + ">"; + case TInst(_.get() => t, []): + return t.name; + case TAbstract(_.get().name => "Null", [param]): + return getSwiftType(param) + "?"; + case TAbstract(_.get().name => "Int", []): + return "Int32"; + case TAbstract(_.get() => t, []): + final isPublicEnumAbstract = t.meta.has(':enum') && !t.isPrivate; + final isIntEnumAbstract = if (isPublicEnumAbstract) { + final underlyingRootType = TypeTools.followWithAbstracts(t.type, false); + Context.unify(underlyingRootType, Context.resolveType(macro :Int, Context.currentPos())); + } else false; + if (isIntEnumAbstract) { + knownEnums[t.name] = hx.strings.Strings.toLowerUnderscore(safeIdent(TypeTools.toString(type))); + } + return t.name; + case TFun(args, ret): + final builder = new hx.strings.StringBuilder(arg ? "@escaping (" : "("); + for (i => arg in args) { + if (i > 0) builder.add(", "); + builder.add(getSwiftType(arg.t)); + } + builder.add(")->"); + builder.add(getSwiftType(ret)); + + return builder.toString(); + case TType(_.get() => t, params): + return getSwiftType(TypeTools.follow(type, true), arg); + default: + Context.fatalError("No implemented Swift type conversion for: " + type, Context.currentPos()); + } + } + + static function convertArgs(builder: hx.strings.StringBuilder, args: Array<{ name: String, opt: Bool, t: haxe.macro.Type }>) { + for (i => arg in args) { + if (i > 0) builder.add(", "); + builder.add(arg.name); + builder.add(": "); + builder.add(getSwiftType(arg.t, true)); + if (arg.opt) { + Context.fatalError("default? " + arg.name, Context.currentPos()); + } + } + } + + static function castToSwift(item: String, type: haxe.macro.Type, canNull = false, isRet = false) { + return switch type { + case TInst(_.get().name => "String", params): + return "useString(" + item + ")" + (canNull ? "" : "!"); + case TInst(_.get().name => "Array", [param]): + if (isRet) { + return + "{" + + "var __ret: UnsafeMutablePointer<UnsafeMutableRawPointer?>? = nil;" + + "let __ret_length = " + ~/\)$/.replace(item, ", &__ret);") + + "return " + castToSwift("__ret", type, canNull, false) + ";" + + "}()"; + } else { + return + "{" + + "let __r = UnsafeMutableBufferPointer<UnsafeMutableRawPointer?>(start: " + item + ", count: " + item + "_length).map({" + + castToSwift("$0", param) + + "});" + + "c_" + libName + "." + libName + "_release(" + item + ");" + + "return __r;" + + "}()"; + } + case TInst(_.get() => t, []): + final wrapper = t.isInterface ? 'Any${t.name}' : t.name; + if (canNull) { + return "(" + item + ").map({ " + wrapper + "($0) })"; + } else { + return wrapper + "(" + item + "!)"; + } + case TAbstract(_.get().name => "Null", [param]): + return castToSwift(item, param, true); + case TAbstract(_.get() => t, []): + return item; + case TType(_.get() => t, params): + return castToSwift(item, TypeTools.follow(type, true), canNull); + default: + Context.fatalError("No implemented Swift cast for: " + type, Context.currentPos()); + } + } + + static function castToC(item: String, type: haxe.macro.Type, canNull = false) { + return switch type { + case TInst(_.get().name => "String", params): + return item; + case TInst(_.get().name => "Array", params): + return item; + case TInst(_.get() => t, []): + return item + (canNull ? "?" : "") + ".o"; + case TAbstract(_.get().name => "Null", [param]): + return castToC(item, param, true); + case TAbstract(_.get() => t, []): + return item; + case TType(_.get() => t, params): + return castToC(item, TypeTools.follow(type, true)); + default: + Context.fatalError("No implemented C cast for: " + type, Context.currentPos()); + } + } + + static function metaIsSwiftExpose(e: ExprDef) { + return switch (e) { + case ECall(call, args): + switch (call.expr) { + case EField(l, r): + switch (l.expr) { + case EConst(CIdent("HaxeSwiftBridge")): + r == "expose"; + default: false; + } + default: false; + } + default: false; + } + } + + static function convertQueuedClass(clsRef: Ref<ClassType>, namespace: String) { + var cls = clsRef.get(); + + // validate + if (cls.isExtern) Context.error('Cannot expose extern directly to Swift', cls.pos); + + var classPrefix = cls.pack.concat([namespace == null ? cls.name : namespace]); + var cNameMeta = getCNameMeta(cls.meta); + + var functionPrefix = + if (cNameMeta != null) + [cNameMeta]; + else + [false ? libName : ""] // NOTE: probably want this if we get packages with differnt name? + .concat(safeIdent(classPrefix.join('.')) != libName ? classPrefix : []) + .filter(s -> s != ''); + + final builder = new hx.strings.StringBuilder(cls.isInterface ? "protocol " : "class "); + builder.add(cls.name); + final superClass = if (cls.superClass == null) { + null; + } else { + final buildMeta = cls.superClass.t.get().meta.extract(":build"); + if (buildMeta.exists(meta -> metaIsSwiftExpose(meta.params[0]?.expr))) { + cls.superClass.t.get(); + } else { + null; + } + } + if (superClass == null) { + builder.add(": SDKObject"); + } else { + builder.add(": "); + builder.add(superClass.name); + } + for (iface in cls.interfaces) { + builder.add(", "); + builder.add(iface.t.get().name); + } + + builder.add(" {\n"); + if (!cls.isInterface && superClass == null) { + builder.add("\tinternal let o: UnsafeMutableRawPointer\n\n\tinternal init(_ ptr: UnsafeMutableRawPointer) {\n\t\to = ptr\n\t}\n\n"); + } + + function convertVar(f: ClassField, read: VarAccess, write: VarAccess) { + final noemit = f.meta.extract("HaxeCBridge.noemit")[0]; + var isConvertibleMethod = f.isPublic && !f.isExtern && (noemit == null || (noemit.params != null && noemit.params.length > 0)); + if (!isConvertibleMethod) return; + if (cls.isInterface) return; + + final cNameMeta = getCNameMeta(f.meta); + + final cFuncNameGet = hx.strings.Strings.toLowerUnderscore(functionPrefix.concat([f.name]).join('_')); + final cFuncNameSet = hx.strings.Strings.toLowerUnderscore(functionPrefix.concat(["set", f.name]).join('_')); + + final cleanDoc = f.doc != null ? StringTools.trim(removeIndentation(f.doc)) : null; + if (cleanDoc != null) builder.add('\t/**\n${cleanDoc.split('\n').map(l -> '\t * ' + l).join('\n')}\n\t */\n'); + + builder.add("\tvar "); + builder.add(f.name); + builder.add(": "); + builder.add(getSwiftType(f.type)); + builder.add(" {\n"); + if (read == AccNormal || read == AccCall) { + builder.add("\t\tget {\n\t\t\t"); + builder.add(castToSwift('c_${libName}.${cFuncNameGet}(o)', f.type, false, true)); + builder.add("\n\t\t}\n"); + } + if (write == AccNormal || write == AccCall) { + builder.add("\t\tset {\n\t\t\tc_"); + builder.add(libName); + builder.add("."); + builder.add(cFuncNameSet); + builder.add("(o, "); + builder.add(castToC("newValue", f.type)); + builder.add(")\n\t\t}\n"); + } + builder.add("\t}\n\n"); + } + + function convertFunction(f: ClassField, kind: SwiftFunctionInfoKind) { + final noemit = f.meta.extract("HaxeCBridge.noemit")[0]; + var isConvertibleMethod = f.isPublic && !f.isExtern && !f.meta.has("HaxeCBridge.wrapper") && (noemit == null || (noemit.params != null && noemit.params.length > 0)); + if (!isConvertibleMethod) return; + if (cls.isInterface) return; + + switch f.type { + case TFun(targs, tret): + final cNameMeta = getCNameMeta(f.meta); + + var cFuncName: String = + if (cNameMeta != null) + cNameMeta; + else if (f.meta.has("HaxeCBridge.wrapper")) + functionPrefix.concat([f.name.substring(0, f.name.length - 7)]).join('_'); + else + functionPrefix.concat([f.name]).join('_'); + + var funcName: String = + if (f.meta.has("HaxeCBridge.wrapper")) + f.name.substring(0, f.name.length - 7); + else + f.name; + + cFuncName = hx.strings.Strings.toLowerUnderscore(cFuncName); + + final cleanDoc = f.doc != null ? StringTools.trim(removeIndentation(f.doc)) : null; + + if (cleanDoc != null) builder.add('\t/**\n${cleanDoc.split('\n').map(l -> '\t * ' + l).join('\n')}\n\t */\n'); + switch kind { + case Constructor: + builder.add("\tinit("); + convertArgs(builder, targs); + builder.add(") {\n\t\to = c_"); + builder.add(libName); + builder.add("."); + builder.add(cFuncName); + builder.add("("); + for (i => arg in targs) { + if (i > 0) builder.add(", "); + builder.add(castToC(arg.name, arg.t)); + } + builder.add(")\n\t}\n\n"); + case Member: + builder.add("\tfunc "); + builder.add(funcName); + builder.add("("); + convertArgs(builder, targs); + builder.add(") -> "); + builder.add(getSwiftType(tret)); + builder.add(" {\n\t\t"); + for (arg in targs) { + switch (arg.t) { + case TFun(fargs, fret): + builder.add("let __"); + builder.add(arg.name); + builder.add("_ptr = UnsafeMutableRawPointer(Unmanaged.passRetained("); + builder.add(arg.name); + builder.add(" as AnyObject).toOpaque())\n\t\t"); + default: + } + } + final ibuilder = new hx.strings.StringBuilder("c_"); + ibuilder.add(libName); + ibuilder.add("."); + ibuilder.add(cFuncName); + ibuilder.add("(\n\t\t\tself.o"); + for (arg in targs) { + ibuilder.add(",\n\t\t\t"); + switch (arg.t) { + case TFun(fargs, fret): + ibuilder.add("{ ("); + for (i => farg in fargs) { + if (i > 0) ibuilder.add(", "); + ibuilder.add("a" + i); + switch (farg.t) { + case TInst(_.get().name => "Array", params): + ibuilder.add(", a" + i + "_length"); + default: + } + } + if (fargs.length > 0) ibuilder.add(", "); + ibuilder.add("ctx"); + // TODO unretained vs retained + ibuilder.add(") in\n\t\t\t\tlet "); + ibuilder.add(arg.name); + ibuilder.add(" = Unmanaged<AnyObject>.fromOpaque(ctx!).takeUnretainedValue() as! "); + ibuilder.add(getSwiftType(arg.t)); + ibuilder.add("\n\t\t\t\t"); + final cbuilder = new hx.strings.StringBuilder(arg.name); + cbuilder.add("("); + for (i => farg in fargs) { + if (i > 0) cbuilder.add(", "); + cbuilder.add(castToSwift("a" + i, farg.t)); + } + cbuilder.add(")"); + ibuilder.add(castToSwift(cbuilder.toString(), fret, false, true)); + ibuilder.add("\n\t\t\t},\n\t\t\t__"); + ibuilder.add(arg.name); + ibuilder.add("_ptr"); + default: + ibuilder.add(castToC(arg.name, arg.t)); + } + } + ibuilder.add("\n\t\t)"); + builder.add(castToSwift(ibuilder.toString(), tret, false, true)); + builder.add("\n\t}\n\n"); + case Static: + Context.fatalError('Swift bridging for statics not implemented yet', f.pos); + } + + default: Context.fatalError('Internal error: Expected function expression', f.pos); + } + } + + if (cls.constructor != null) { + convertFunction(cls.constructor.get(), Constructor); + } + + for (f in cls.statics.get()) { + convertFunction(f, Static); + } + + for (f in cls.fields.get()) { + switch (f.kind) { + case FMethod(MethMacro): + case FMethod(_): convertFunction(f, Member); + case FVar(read, write): convertVar(f, read, write); + } + } + + if (!cls.isInterface && superClass == null) { + builder.add("\tdeinit {\n\t\tc_"); + builder.add(libName); + builder.add("."); + builder.add(libName); + builder.add("_release(o)\n\t}\n"); + } + + builder.add("}\n"); + + if (cls.isInterface) { + // TODO: extension with defaults for all exposed methods + builder.add("\nclass Any"); + builder.add(cls.name); + builder.add(": "); + builder.add(cls.name); + builder.add(" {\n"); + builder.add("\tinternal let o: UnsafeMutableRawPointer\n\n\tinternal init(_ ptr: UnsafeMutableRawPointer) {\n\t\to = ptr\n\t}\n\n"); + builder.add("\tdeinit {\n\t\tc_"); + builder.add(libName); + builder.add("."); + builder.add(libName); + builder.add("_release(o)\n\t}\n"); + builder.add("\n}\n"); + } + + return builder.toString(); + } + + static macro function runUserMain() { + var mainClassPath = getMainFromHaxeArgs(Sys.args()); + if (mainClassPath == null) { + return macro null; + } else { + return Context.parse('$mainClassPath.main()', Context.currentPos()); + } + } + + static function isLibraryBuild() { + return Context.defined('dll_link') || Context.defined('static_link'); + } + + static function isDynamicLink() { + return Context.defined('dll_link'); + } + + static function getCNameMeta(meta: MetaAccess): Null<String> { + var cNameMeta = meta.extract('HaxeCBridge.name')[0]; + return if (cNameMeta != null) { + switch cNameMeta.params { + case [{expr: EConst(CString(name))}]: + safeIdent(name); + default: + Context.error('Incorrect usage, syntax is @${cNameMeta.name}(name: String)', cNameMeta.pos); + } + } else null; + } + + static function generateImplementation() { + return code(' + import c_' + libName + ' + + func setup(_ handler: @convention(c) @escaping (UnsafePointer<CChar>?)->Void) { + c_' + libName + '.' + libName + '_setup(handler) + } + + func stop(_ wait: Bool) { + c_' + libName + '.' + libName + '_stop(wait) + } + + internal protocol SDKObject { + var o: UnsafeMutableRawPointer {get} + } + + internal func useString(_ mptr: UnsafePointer<CChar>?) -> String? { + if let ptr = mptr { + let r = String(cString: ptr) + c_' + libName + '.' + libName + '_release(ptr) + return r + } else { + return nil + } + } + + internal func useString(_ mptr: UnsafeMutableRawPointer?) -> String? { + return useString(UnsafePointer(mptr?.assumingMemoryBound(to: CChar.self))) + } + + ') + + queuedClasses.map(c -> convertQueuedClass(c.cls, c.namespace)).join("\n") + "\n" + + { iterator: () -> knownEnums.keyValueIterator() }.map(e -> "typealias " + e.key + " = " + e.value + "\n").join("\n") + ; + } + + /** + We determine a project name to be the `--main` startup class + + The user can override this with `-D HaxeCBridge.name=ExampleName` + + This isn't rigorously defined but hopefully will produced nicely namespaced and unsurprising function names + **/ + static function getLibNameFromHaxeArgs(): Null<String> { + var overrideName = Context.definedValue('HaxeCBridge.name'); + if (overrideName != null && overrideName != '') { + return safeIdent(overrideName); + } + + var args = Sys.args(); + + var mainClassPath = getMainFromHaxeArgs(args); + if (mainClassPath != null) { + return safeIdent(mainClassPath); + } + + // no lib name indicator found in args + return null; + } + + static function getMainFromHaxeArgs(args: Array<String>): Null<String> { + for (i in 0...args.length) { + var arg = args[i]; + switch arg { + case '-m', '-main', '--main': + var classPath = args[i + 1]; + return classPath; + default: + } + } + return null; + } + + static function safeIdent(str: String) { + // replace non a-z0-9_ with _ + str = ~/[^\w]/gi.replace(str, '_'); + // replace leading number with _ + str = ~/^[^a-z_]/i.replace(str, '_'); + // replace empty string with _ + str = str == '' ? '_' : str; + return str; + } + +} + +enum SwiftFunctionInfoKind { + Constructor; + Member; + Static; +} + + #end // (display || display_details || target.name != cpp) + +#elseif (cpp && !cppia) +// runtime HaxeSwiftBridge + +class HaxeSwiftBridge {} + +#end diff --git a/snikket/Chat.hx b/snikket/Chat.hx index 366d7b1..7c905e9 100644 --- a/snikket/Chat.hx +++ b/snikket/Chat.hx @@ -34,6 +34,7 @@ class UiStateImpl { #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end abstract class Chat { private var client:Client; @@ -390,6 +391,7 @@ abstract class Chat { @:expose #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end class DirectChat extends Chat { @:allow(snikket) @@ -580,6 +582,7 @@ class DirectChat extends Chat { @:expose #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end class Channel extends Chat { @:allow(snikket) @@ -968,6 +971,7 @@ class Channel extends Chat { @:expose #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end class AvailableChat { /** diff --git a/snikket/ChatMessage.hx b/snikket/ChatMessage.hx index 6bc4bda..06205f9 100644 --- a/snikket/ChatMessage.hx +++ b/snikket/ChatMessage.hx @@ -17,14 +17,22 @@ import snikket.XEP0393; import snikket.EmojiUtil; import snikket.Message; +@:expose +@:nullSafety(Strict) +#if cpp +@:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end class ChatAttachment { public final name: Null<String>; public final mime: String; public final size: Null<Int>; public final uris: Array<String>; + @HaxeCBridge.noemit public final hashes: Array<{algo:String, hash:BytesData}>; - public function new(name: Null<String>, mime: String, size: Null<Int>, uris: Array<String>, hashes: Array<{algo:String, hash:BytesData}>) { + @:allow(snikket) + private function new(name: Null<String>, mime: String, size: Null<Int>, uris: Array<String>, hashes: Array<{algo:String, hash:BytesData}>) { this.name = name; this.mime = mime; this.size = size; @@ -37,6 +45,7 @@ class ChatAttachment { @:nullSafety(Strict) #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end class ChatMessage { /** @@ -82,10 +91,11 @@ class ChatMessage { /** Array of attachments to this message **/ - public var attachments: Array<ChatAttachment> = []; + public var attachments (default, null): Array<ChatAttachment> = []; /** Map of reactions to this message **/ + @HaxeCBridge.noemit public var reactions: Map<String, Array<String>> = []; /** @@ -115,7 +125,8 @@ class ChatMessage { /** Array of past versions of this message, if it has been edited **/ - public var versions: Array<ChatMessage> = []; + @:allow(snikket) + public var versions (default, null): Array<ChatMessage> = []; @:allow(snikket) private var payloads: Array<Stanza> = []; diff --git a/snikket/Client.hx b/snikket/Client.hx index 83022ae..4390931 100644 --- a/snikket/Client.hx +++ b/snikket/Client.hx @@ -35,6 +35,7 @@ import HaxeCBridge; @:expose #if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) #end class Client extends EventEmitter { private var stream:GenericStream; @@ -440,7 +441,7 @@ class Client extends EventEmitter { /** Get the current display name for this account - @returns display name or NULL + @returns display name **/ public function displayName() { return _displayName; diff --git a/snikket/Persistence.hx b/snikket/Persistence.hx index 7cc9adf..8f945e3 100644 --- a/snikket/Persistence.hx +++ b/snikket/Persistence.hx @@ -5,10 +5,14 @@ import snikket.Chat; import snikket.ChatMessage; import snikket.Message; +#if cpp +@:build(HaxeSwiftBridge.expose()) +#end interface Persistence { public function lastId(accountId: String, chatId: Null<String>, callback:(serverId:Null<String>)->Void):Void; public function storeChat(accountId: String, chat: Chat):Void; public function getChats(accountId: String, callback: (chats:Array<SerializedChat>)->Void):Void; + @HaxeCBridge.noemit public function getChatsUnreadDetails(accountId: String, chats: Array<Chat>, callback: (details:Array<{ chatId: String, message: ChatMessage, unreadCount: Int }>)->Void):Void; public function storeReaction(accountId: String, update: ReactionUpdate, callback: (Null<ChatMessage>)->Void):Void; public function storeMessage(accountId: String, message: ChatMessage, callback: (ChatMessage)->Void):Void; @@ -23,5 +27,6 @@ interface Persistence { public function storeStreamManagement(accountId:String, smId:String, outboundCount:Int, inboundCount:Int, outboundQueue:Array<String>):Void; public function getStreamManagement(accountId:String, callback: (smId:Null<String>, outboundCount:Int, inboundCount:Int, outboundQueue:Array<String>)->Void):Void; public function storeService(accountId:String, serviceId:String, name:Null<String>, node:Null<String>, caps:Caps):Void; + @HaxeCBridge.noemit public function findServicesWithFeature(accountId:String, feature:String, callback:(Array<{serviceId:String, name:Null<String>, node:Null<String>, caps: Caps}>)->Void):Void; } diff --git a/snikket/jingle/PeerConnection.cpp.hx b/snikket/jingle/PeerConnection.cpp.hx index 58c7ef0..fa85cd0 100644 --- a/snikket/jingle/PeerConnection.cpp.hx +++ b/snikket/jingle/PeerConnection.cpp.hx @@ -1,12 +1,26 @@ package snikket.jingle; +#if cpp +import HaxeCBridge; +#end + typedef TODO = Dynamic; -typedef DTMFSender = TODO; typedef Transceiver = { receiver: Null<{ track: MediaStreamTrack }>, sender: Null<{ track: MediaStreamTrack, dtmf: DTMFSender }> } +#if cpp +@:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end +class DTMFSender { +} + +#if cpp +@:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end class MediaStreamTrack { public var muted: Bool; public var kind: String; @@ -14,8 +28,12 @@ class MediaStreamTrack { public function stop() { } } +#if cpp +@:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end class MediaStream { - public function getTracks() { + public function getTracks(): Array<MediaStreamTrack> { return []; } } diff --git a/snikket/jingle/Session.hx b/snikket/jingle/Session.hx index fade99a..b1a753b 100644 --- a/snikket/jingle/Session.hx +++ b/snikket/jingle/Session.hx @@ -5,6 +5,9 @@ import snikket.jingle.PeerConnection; import snikket.jingle.SessionDescription; using Lambda; +#if cpp +@:build(HaxeSwiftBridge.expose()) +#end interface Session { public var sid (get, null): String; public function initiate(stanza: Stanza): InitiatedSession; diff --git a/snikket/persistence/Sqlite.hx b/snikket/persistence/Sqlite.hx index 2818aa1..e929542 100644 --- a/snikket/persistence/Sqlite.hx +++ b/snikket/persistence/Sqlite.hx @@ -20,7 +20,10 @@ import snikket.Message; // TODO: consider doing background threads for operations @:expose +#if cpp @:build(HaxeCBridge.expose()) +@:build(HaxeSwiftBridge.expose()) +#end class Sqlite implements Persistence { final db: Connection; final blobpath: String;