1// Based on GPLv3 code from deltachat-android
2// https://github.com/deltachat/deltachat-android/blob/master/res/raw/webxdc.js
3
4window.webxdc = (() => {
5 let setUpdateListenerPromise = null
6 var update_listener = () => {};
7 var last_serial = 0;
8 var realtime_listener = (data) => {};
9
10 window.__webxdcUpdate = () => {
11 var updates = JSON.parse(InternalJSApi.getStatusUpdates(last_serial));
12 updates.forEach((update) => {
13 update_listener(update);
14 last_serial = update.serial;
15 });
16 if (setUpdateListenerPromise) {
17 setUpdateListenerPromise();
18 setUpdateListenerPromise = null;
19 }
20 };
21
22 window.__webxdcRealtimeData = (data) => {
23 realtime_listener(Uint8Array.from(atob(data), c => c.charCodeAt(0)));
24 };
25
26 return {
27 selfAddr: InternalJSApi.selfAddr(),
28
29 selfName: InternalJSApi.selfName(),
30
31 setUpdateListener: (cb, serial) => {
32 last_serial = typeof serial === "undefined" ? 0 : parseInt(serial);
33 update_listener = cb;
34 var promise = new Promise((res, _rej) => {
35 setUpdateListenerPromise = res;
36 });
37 window.__webxdcUpdate();
38 return promise;
39 },
40
41 sendUpdate: (payload, descr) => {
42 InternalJSApi.sendStatusUpdate(JSON.stringify(payload), descr);
43 },
44
45 importFiles: (filters) => {
46 var element = document.createElement("input");
47 element.type = "file";
48 element.accept = [
49 ...(filters.extensions || []),
50 ...(filters.mimeTypes || []),
51 ].join(",");
52 element.multiple = filters.multiple || false;
53 const promise = new Promise((resolve, _reject) => {
54 element.onchange = (_ev) => {
55 const files = Array.from(element.files || []);
56 document.body.removeChild(element);
57 resolve(files);
58 };
59 });
60 element.style.display = "none";
61 document.body.appendChild(element);
62 element.click();
63 return promise;
64 },
65
66 sendToChat: async (message) => {
67 const data = {};
68 if (!message.file && !message.text) {
69 return Promise.reject("sendToChat() error: file or text missing");
70 }
71 const blobToBase64 = (file) => {
72 const dataStart = ";base64,";
73 return new Promise((resolve, reject) => {
74 const reader = new FileReader();
75 reader.readAsDataURL(file);
76 reader.onload = () => {
77 let data = reader.result;
78 resolve(data.slice(data.indexOf(dataStart) + dataStart.length));
79 };
80 reader.onerror = () => reject(reader.error);
81 });
82 };
83 if (message.text) {
84 data.text = message.text;
85 }
86
87 if (message.file) {
88 let base64content;
89 if (!message.file.name) {
90 return Promise.reject("sendToChat() error: file name missing");
91 }
92 if (
93 Object.keys(message.file).filter((key) =>
94 ["blob", "base64", "plainText"].includes(key)
95 ).length > 1
96 ) {
97 return Promise.reject("sendToChat() error: only one of blob, base64 or plainText allowed");
98 }
99
100 if (message.file.blob instanceof Blob) {
101 base64content = await blobToBase64(message.file.blob);
102 } else if (typeof message.file.base64 === "string") {
103 base64content = message.file.base64;
104 } else if (typeof message.file.plainText === "string") {
105 base64content = await blobToBase64(
106 new Blob([message.file.plainText])
107 );
108 } else {
109 return Promise.reject("sendToChat() error: none of blob, base64 or plainText set correctly");
110 }
111 data.base64 = base64content;
112 data.name = message.file.name;
113 }
114
115 const errorMsg = InternalJSApi.sendToChat(JSON.stringify(data));
116 if (errorMsg) {
117 return Promise.reject(errorMsg);
118 }
119 },
120
121 joinRealtimeChannel: () => {
122 return {
123 leave: () => {},
124 send: (data) => {
125 if (!(data instanceof Uint8Array)) {
126 throw new Error('realtime listener data must be a Uint8Array')
127 }
128 InternalJSApi.sendRealtime(data);
129 },
130 setListener: (listener) => {
131 realtime_listener = listener;
132 }
133 };
134 },
135
136 };
137})();