diff --git a/src/main/java/de/monocles/chat/WebxdcPage.java b/src/main/java/de/monocles/chat/WebxdcPage.java index f872df4ed..a8395ee03 100644 --- a/src/main/java/de/monocles/chat/WebxdcPage.java +++ b/src/main/java/de/monocles/chat/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; @@ -37,6 +38,7 @@ import androidx.core.graphics.drawable.IconCompat; import androidx.core.util.Consumer; import androidx.databinding.DataBindingUtil; +import com.google.android.material.color.MaterialColors; import com.google.common.io.ByteStreams; import io.ipfs.cid.Cid; @@ -148,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); @@ -301,7 +310,7 @@ public class WebxdcPage implements ConversationPage { TextView tv = (TextView) v.findViewById(android.R.id.text1); tv.setGravity(Gravity.CENTER); tv.setTextColor(ContextCompat.getColor(context, R.color.white)); - tv.setBackgroundColor(UIHelper.getColorForName(getItem(position))); + tv.setBackgroundColor(MaterialColors.harmonizeWithPrimary(activity.get(),UIHelper.getColorForName(getItem(position)))); return v; } }); @@ -323,6 +332,7 @@ public class WebxdcPage implements ConversationPage { } ShortcutManagerCompat.requestPinShortcut(xmppConnectionService, builder.build(), null); } else { + binding.webview.loadUrl("about:blank"); remover.accept(WebxdcPage.this); } }); @@ -336,9 +346,11 @@ public class WebxdcPage implements ConversationPage { } public void refresh() { - if (binding != null && binding.webview != null) { - binding.webview.post(() -> binding.webview.loadUrl("javascript:__webxdcUpdate();")); - } + 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() { @@ -353,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(), "@/+"); } @@ -462,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/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index bcf6179a9..6f80e24be 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -1702,6 +1702,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); } @@ -1835,6 +1839,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 c7c6860c8..7af5fb875 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -682,16 +682,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(); } diff --git a/src/main/res/raw/webxdc.js b/src/main/res/raw/webxdc.js index 9f03e5b34..ca82e2a3d 100644 --- a/src/main/res/raw/webxdc.js +++ b/src/main/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(), @@ -112,5 +117,21 @@ window.webxdc = (() => { return Promise.reject(errorMsg); } }, + + 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; + } + }; + }, + }; })();