diff --git a/src/cheogram/java/com/cheogram/android/WebxdcPage.java b/src/cheogram/java/com/cheogram/android/WebxdcPage.java index 12715a50ba640073b9b3cdd067214d20171d3d89..2f22b40d88774ea2b9e58688bbc1a1a455ff341c 100644 --- a/src/cheogram/java/com/cheogram/android/WebxdcPage.java +++ b/src/cheogram/java/com/cheogram/android/WebxdcPage.java @@ -18,6 +18,7 @@ import android.view.LayoutInflater; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.util.Base64; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; @@ -149,6 +150,13 @@ public class WebxdcPage implements ConversationPage { return "webxdc\0" + source.getUuid(); } + public boolean threadMatches(final Element thread) { + if (thread == null) return false; + if (thread.getContent() == null) return false; + if (source.getThread() == null) return false; + return thread.getContent().equals(source.getThread().getContent()); + } + public boolean openUri(Uri uri) { Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -324,6 +332,7 @@ public class WebxdcPage implements ConversationPage { } ShortcutManagerCompat.requestPinShortcut(xmppConnectionService, builder.build(), null); } else { + binding.webview.loadUrl("about:blank"); remover.accept(WebxdcPage.this); } }); @@ -340,6 +349,10 @@ public class WebxdcPage implements ConversationPage { binding.webview.post(() -> binding.webview.loadUrl("javascript:__webxdcUpdate();")); } + public void realtimeData(String base64) { + binding.webview.post(() -> binding.webview.loadUrl("javascript:__webxdcRealtimeData('" + base64.replace("'", "").replace("\\", "").replace("+", "%2B") + "');")); + } + protected Jid selfJid() { final Conversation conversation = (Conversation) source.getConversation(); if (conversation.getMode() == Conversation.MODE_MULTI && !conversation.getMucOptions().nonanonymous()) { @@ -352,6 +365,11 @@ public class WebxdcPage implements ConversationPage { protected class InternalJSApi { @JavascriptInterface public String selfAddr() { + final Conversation conversation = (Conversation) source.getConversation(); + if (conversation.getMode() == Conversation.MODE_MULTI && !conversation.getMucOptions().nonanonymous()) { + final var occupantId = conversation.getMucOptions().getSelf().getOccupantId(); + if (occupantId != null) return occupantId; + } return "xmpp:" + Uri.encode(selfJid().toEscapedString(), "@/+"); } @@ -461,5 +479,20 @@ public class WebxdcPage implements ConversationPage { return e.toString(); } } + + @JavascriptInterface + public void sendRealtime(byte[] data) { + Message message = new Message(source.getConversation(), null, Message.ENCRYPTION_NONE); + message.addPayload(new Element("no-store", "urn:xmpp:hints")); + Element webxdc = new Element("x", "urn:xmpp:webxdc:0"); + message.addPayload(webxdc); + webxdc.addChild("data").setContent(Base64.encodeToString(data, Base64.NO_WRAP)); + message.setThread(source.getThread()); + if (source.isPrivateMessage()) { + Message.configurePrivateMessage(message, source.getCounterpart()); + } + message.setBody((String) null); + xmppConnectionService.sendMessage(message); + } } } diff --git a/src/cheogram/res/raw/webxdc.js b/src/cheogram/res/raw/webxdc.js index 31404e1ae71491ab7587cc579f77dbffefa98582..ca82e2a3d0a1041ca8dc412d486d19cdc442f746 100644 --- a/src/cheogram/res/raw/webxdc.js +++ b/src/cheogram/res/raw/webxdc.js @@ -5,6 +5,7 @@ window.webxdc = (() => { let setUpdateListenerPromise = null var update_listener = () => {}; var last_serial = 0; + var realtime_listener = (data) => {}; window.__webxdcUpdate = () => { var updates = JSON.parse(InternalJSApi.getStatusUpdates(last_serial)); @@ -18,6 +19,10 @@ window.webxdc = (() => { } }; + window.__webxdcRealtimeData = (data) => { + realtime_listener(Uint8Array.from(atob(data), c => c.charCodeAt(0))); + }; + return { selfAddr: InternalJSApi.selfAddr(), @@ -113,5 +118,20 @@ window.webxdc = (() => { } }, + joinRealtimeChannel: () => { + return { + leave: () => {}, + send: (data) => { + if (!(data instanceof Uint8Array)) { + throw new Error('realtime listener data must be a Uint8Array') + } + InternalJSApi.sendRealtime(data); + }, + setListener: (listener) => { + realtime_listener = listener; + } + }; + }, + }; })(); diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 601dcf75b63c11e5c0e686b6d30a52f4b6c7f17d..bd35361740364d8202dfc547259ee86f06350feb 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -1440,6 +1440,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl pagerAdapter.startWebxdc(page); } + public void webxdcRealtimeData(final Element thread, final String base64) { + pagerAdapter.webxdcRealtimeData(thread, base64); + } + public void startCommand(Element command, XmppConnectionService xmppConnectionService) { pagerAdapter.startCommand(command, xmppConnectionService); } @@ -1570,6 +1574,18 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } + public void webxdcRealtimeData(final Element thread, final String base64) { + if (sessions == null) return; + + for (ConversationPage session : sessions) { + if (session instanceof WebxdcPage) { + if (((WebxdcPage) session).threadMatches(thread)) { + ((WebxdcPage) session).realtimeData(base64); + } + } + } + } + public void startWebxdc(WebxdcPage page) { show(); sessions.add(page); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 5540a0603b5c3f4584489772b3bbdec17f1e5880..84a360d31432db078807fa66b315bff124d985d6 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -583,16 +583,24 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece webxdcSender = counterpart; } } - mXmppConnectionService.insertWebxdcUpdate(new WebxdcUpdate( - conversation, - remoteMsgId, - counterpart, - thread, - body == null ? null : body.content, - webxdc.findChildContent("document", "urn:xmpp:webxdc:0"), - webxdc.findChildContent("summary", "urn:xmpp:webxdc:0"), - webxdc.findChildContent("json", "urn:xmpp:json:0") - )); + final var document = webxdc.findChildContent("document", "urn:xmpp:webxdc:0"); + final var summary = webxdc.findChildContent("summary", "urn:xmpp:webxdc:0"); + final var payload = webxdc.findChildContent("json", "urn:xmpp:json:0"); + if (document != null || summary != null || payload != null) { + mXmppConnectionService.insertWebxdcUpdate(new WebxdcUpdate( + conversation, + remoteMsgId, + counterpart, + thread, + body == null ? null : body.content, + document, + summary, + payload + )); + } + + final var realtime = webxdc.findChildContent("data", "urn:xmpp:webxdc:0"); + if (realtime != null) conversation.webxdcRealtimeData(thread, realtime); mXmppConnectionService.updateConversationUi(); }