webxdc.js

  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})();