Detailed changes
@@ -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);
+ }
}
}
@@ -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;
+ }
+ };
+ },
+
};
})();
@@ -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);
@@ -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();
}