From c83ef1cc543d08cc6cd26b477be2b8df9dd01e1a Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Thu, 11 Jul 2024 11:15:19 +0200 Subject: [PATCH] pages: work in progress [might break] --- example/webxdc/.gitignore | 2 + example/webxdc/LICENSE | 24 +++ example/webxdc/README.md | 74 ++++++++ example/webxdc/create-xdc.sh | 27 +++ example/webxdc/icon.png | Bin 0 -> 16283 bytes example/webxdc/index.html | 67 ++++++++ example/webxdc/manifest.toml | 2 + example/webxdc/webxdc.js | 324 +++++++++++++++++++++++++++++++++++ 8 files changed, 520 insertions(+) create mode 100644 example/webxdc/.gitignore create mode 100644 example/webxdc/LICENSE create mode 100644 example/webxdc/README.md create mode 100755 example/webxdc/create-xdc.sh create mode 100644 example/webxdc/icon.png create mode 100644 example/webxdc/index.html create mode 100644 example/webxdc/manifest.toml create mode 100644 example/webxdc/webxdc.js diff --git a/example/webxdc/.gitignore b/example/webxdc/.gitignore new file mode 100644 index 0000000..97f3520 --- /dev/null +++ b/example/webxdc/.gitignore @@ -0,0 +1,2 @@ +*.xdc + diff --git a/example/webxdc/LICENSE b/example/webxdc/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/example/webxdc/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/example/webxdc/README.md b/example/webxdc/README.md new file mode 100644 index 0000000..793abfe --- /dev/null +++ b/example/webxdc/README.md @@ -0,0 +1,74 @@ +# Hello + +Sample project with a simple implementation of the webxdc read and write APIs. + + +## Demo (no server or installation required) + +1. Open `index.html` in your web browser +2. Click 'Add Peer' to open as many peers as you like +3. Type a message and press 'Send' to see the update in each peer. (For Safari you might need to check the setting under Develop > Disable Local File Restrictions.) + + +## Developing webxdc apps + +Simply copy `webxdc.js` from this repo beside your `index.html` and you are ready to go +to **develop and test your app in most browsers.** + +Bundle your app using `./create-xdc.sh your-app-name` +and **send it to your friends** 🙂 + +Screenshot 2023-02-10 at 20 40 22 + + +## Further Hints and Troubleshooting + + +### Limitations + +Due to the nature of most browsers and how they scope `localStorage`, +each emulated peer will get the same `localStorage`. + +To really test the storage usage of your Webxdc, +bundle the app and test it in Delta Chat directly +where all peers get their own `localStorage`. +Alternatively, use the more advanced [webxdc-dev](https://github.com/webxdc/webxdc-dev) tool. + + +### Type-checking and completion + +If you are using VSCode you can have autocompletion and type-checking even without using TypeScript by adding these two lines to your JavaScript source files: + +```js +//@ts-check +/** @typedef {import('./webxdc').Webxdc} Webxdc */ +``` + +Without VSCode you need to install TypeScript and then run the check manually. + +```sh +npm -g typescript +tsc --noEmit --allowJs --lib es2016,dom webxdc.js # to check if types and simulator are in sync +tsc --noEmit --allowJs --lib es2016,dom your_js_file.js +``` + +### Developing in Safari + +To use the devtool in safari you need to disable the local file restrictions +under `Develop` -> `Disable Local File Restrictions`. + +After doing this you can use the dev tool simulator. + +Make sure to reload (`Cmd + R`) all simulator tabs/windows to apply this setting. +Without this option `Add Peer` seems to work (it opens a new instance), but **the instances will not be able to communicate**. + + +### Developing on Android + +- install Termux +- install Python and Git in Termux +- `git clone` the devtool repo or your fork of it +- use `python -m http.server` to serve it for development using nano/vim +- when you are done, use `./create-xdc.sh` for bundling +- copy the created `.xdc` file to a location from where you can access and send it via Delta Chat +- pro tip: you can create symbolic link to a folder in the external storage diff --git a/example/webxdc/create-xdc.sh b/example/webxdc/create-xdc.sh new file mode 100755 index 0000000..eeb26c8 --- /dev/null +++ b/example/webxdc/create-xdc.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +case "$1" in + "-h" | "--help") + echo "usage: ${0##*/} [PACKAGE_NAME]" + exit + ;; + "") + PACKAGE_NAME=${PWD##*/} # '##*/' removes everything before the last slash and the last slash + ;; + *) + PACKAGE_NAME=${1%.xdc} # '%.xdc' removes the extension and allows PACKAGE_NAME to be given with or without extension + ;; +esac + +rm "$PACKAGE_NAME.xdc" 2> /dev/null +zip -9 --recurse-paths "$PACKAGE_NAME.xdc" --exclude LICENSE README.md webxdc.js "./*.sh" "./*.xdc" -- * + +echo "success, archive contents:" +unzip -l "$PACKAGE_NAME.xdc" + +# check package size +MAXSIZE=655360 +size=$(wc -c < "$PACKAGE_NAME.xdc") +if [ "$size" -ge $MAXSIZE ]; then + echo "WARNING: package size exceeded the limit ($size > $MAXSIZE)" +fi diff --git a/example/webxdc/icon.png b/example/webxdc/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a5ed8deabede31b3a237c2be261f9b221572af1 GIT binary patch literal 16283 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 literal 0 HcmV?d00001 diff --git a/example/webxdc/index.html b/example/webxdc/index.html new file mode 100644 index 0000000..2db6a44 --- /dev/null +++ b/example/webxdc/index.html @@ -0,0 +1,67 @@ + + + + Hello + + + + + + +

Hello

+
+ + +
+

+

+ + + diff --git a/example/webxdc/manifest.toml b/example/webxdc/manifest.toml new file mode 100644 index 0000000..9f0f2ce --- /dev/null +++ b/example/webxdc/manifest.toml @@ -0,0 +1,2 @@ +name = "Hello" +source_code_url = "https://github.com/webxdc/hello" diff --git a/example/webxdc/webxdc.js b/example/webxdc/webxdc.js new file mode 100644 index 0000000..8fb5ee7 --- /dev/null +++ b/example/webxdc/webxdc.js @@ -0,0 +1,324 @@ +// This file originates from +// https://github.com/webxdc/hello/blob/master/webxdc.js +// It's a stub `webxdc.js` that adds a webxdc API stub for easy testing in +// browsers. In an actual webxdc environment (e.g. Delta Chat messenger) this +// file is not used and will automatically be replaced with a real one. +// See https://docs.webxdc.org/spec.html#webxdc-api +let ephemeralUpdateKey = "__xdcEphemeralUpdateKey__"; + +/** + * @typedef {import('./webxdc.d.ts').RealtimeListener} RT + * @type {RT} + */ +class RealtimeListener { + constructor() { + this.listener = null; + this.trashed = false; + } + + is_trashed() { + return this.trashed; + } + + receive(data) { + if (this.trashed) { + throw new Error("realtime listener is trashed and can no longer be used"); + } + if (this.listener) { + this.listener(data); + } + } + + setListener(listener) { + this.listener = listener; + } + + send(data) { + if (!data instanceof Uint8Array) { + throw new Error("realtime listener data must be a Uint8Array"); + } + window.localStorage.setItem( + ephemeralUpdateKey, + JSON.stringify([window.webxdc.selfAddr, Array.from(data), Date.now()]) // Date.now() is needed to trigger the event + ); + } + + leave() { + this.trashed = true; + } +} + +// debug friend: document.writeln(JSON.stringify(value)); +//@ts-check +/** @type {import('./webxdc').Webxdc} */ +window.webxdc = (() => { + var updateListener = (_) => {}; + /** + * @type {RT | null} + */ + var realtimeListener = null; + var updatesKey = "__xdcUpdatesKey__"; + window.addEventListener("storage", (event) => { + if (event.key == null) { + window.location.reload(); + } else if (event.key === updatesKey) { + var updates = JSON.parse(event.newValue); + var update = updates[updates.length - 1]; + update.max_serial = updates.length; + console.log("[Webxdc] " + JSON.stringify(update)); + updateListener(update); + } else if (event.key === ephemeralUpdateKey) { + var [sender, update] = JSON.parse(event.newValue); + if (window.webxdc.selfAddr !== sender && realtimeListener && !realtimeListener.is_trashed()) { + realtimeListener.receive( Uint8Array.from(update)); + } + } + }); + + function getUpdates() { + var updatesJSON = window.localStorage.getItem(updatesKey); + return updatesJSON ? JSON.parse(updatesJSON) : []; + } + + var params = new URLSearchParams(window.location.hash.substr(1)); + return { + selfAddr: params.get("addr") || "device0@local.host", + selfName: params.get("name") || "device0", + setUpdateListener: (cb, serial = 0) => { + var updates = getUpdates(); + var maxSerial = updates.length; + updates.forEach((update) => { + if (update.serial > serial) { + update.max_serial = maxSerial; + cb(update); + } + }); + updateListener = cb; + return Promise.resolve(); + }, + joinRealtimeChannel: (cb) => { + if (realtimeListener && realtimeListener.is_trashed()) { + return; + } + rt = new RealtimeListener(); + // mimic connection establishment time + setTimeout(() => realtimeListener = rt, 500); + return rt; + }, + getAllUpdates: () => { + console.log("[Webxdc] WARNING: getAllUpdates() is deprecated."); + return Promise.resolve([]); + }, + sendUpdate: (update, description) => { + var updates = getUpdates(); + var serial = updates.length + 1; + var _update = { + payload: update.payload, + summary: update.summary, + info: update.info, + document: update.document, + serial: serial, + }; + updates.push(_update); + window.localStorage.setItem(updatesKey, JSON.stringify(updates)); + _update.max_serial = serial; + console.log( + '[Webxdc] description="' + description + '", ' + JSON.stringify(_update) + ); + updateListener(_update); + }, + sendToChat: async (content) => { + if (!content.file && !content.text) { + alert("🚨 Error: either file or text need to be set. (or both)"); + return Promise.reject( + "Error from sendToChat: either file or text need to be set" + ); + } + + /** @type {(file: Blob) => Promise} */ + const blob_to_base64 = (file) => { + const data_start = ";base64,"; + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + /** @type {string} */ + //@ts-ignore + let data = reader.result; + resolve(data.slice(data.indexOf(data_start) + data_start.length)); + }; + reader.onerror = () => reject(reader.error); + }); + }; + + let base64Content; + if (content.file) { + if (!content.file.name) { + return Promise.reject("file name is missing"); + } + if ( + Object.keys(content.file).filter((key) => + ["blob", "base64", "plainText"].includes(key) + ).length > 1 + ) { + return Promise.reject( + "you can only set one of `blob`, `base64` or `plainText`, not multiple ones" + ); + } + + // @ts-ignore - needed because typescript imagines that blob would not exist + if (content.file.blob instanceof Blob) { + // @ts-ignore - needed because typescript imagines that blob would not exist + base64Content = await blob_to_base64(content.file.blob); + // @ts-ignore - needed because typescript imagines that base64 would not exist + } else if (typeof content.file.base64 === "string") { + // @ts-ignore - needed because typescript imagines that base64 would not exist + base64Content = content.file.base64; + // @ts-ignore - needed because typescript imagines that plainText would not exist + } else if (typeof content.file.plainText === "string") { + base64Content = await blob_to_base64( + // @ts-ignore - needed because typescript imagines that plainText would not exist + new Blob([content.file.plainText]) + ); + } else { + return Promise.reject( + "data is not set or wrong format, set one of `blob`, `base64` or `plainText`, see webxdc documentation for sendToChat" + ); + } + } + const msg = `The app would now close and the user would select a chat to send this message:\nText: ${ + content.text ? `"${content.text}"` : "No Text" + }\nFile: ${ + content.file + ? `${content.file.name} - ${base64Content.length} bytes` + : "No File" + }`; + if (content.file) { + const confirmed = confirm( + msg + "\n\nDownload the file in the browser instead?" + ); + if (confirmed) { + var element = document.createElement("a"); + element.setAttribute( + "href", + "data:application/octet-stream;base64," + base64Content + ); + element.setAttribute("download", content.file.name); + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + } + } else { + alert(msg); + } + }, + importFiles: (filters) => { + var element = document.createElement("input"); + element.type = "file"; + element.accept = [ + ...(filters.extensions || []), + ...(filters.mimeTypes || []), + ].join(","); + element.multiple = filters.multiple || false; + const promise = new Promise((resolve, _reject) => { + element.onchange = (_ev) => { + console.log("element.files", element.files); + const files = Array.from(element.files || []); + document.body.removeChild(element); + resolve(files); + }; + }); + element.style.display = "none"; + document.body.appendChild(element); + element.click(); + console.log(element); + return promise; + }, + }; +})(); + +window.addXdcPeer = () => { + var loc = window.location; + // get next peer ID + var params = new URLSearchParams(loc.hash.substr(1)); + var peerId = Number(params.get("next_peer")) || 1; + + // open a new window + var peerName = "device" + peerId; + var url = + loc.protocol + + "//" + + loc.host + + loc.pathname + + "#name=" + + peerName + + "&addr=" + + peerName + + "@local.host"; + window.open(url); + + // update next peer ID + params.set("next_peer", String(peerId + 1)); + window.location.hash = "#" + params.toString(); +}; + +window.clearXdcStorage = () => { + window.localStorage.clear(); + window.location.reload(); +}; + +window.alterXdcApp = () => { + var styleControlPanel = + "position: fixed; bottom:1em; left:1em; background-color: #000; opacity:0.8; padding:.5em; font-size:16px; font-family: sans-serif; color:#fff; z-index: 9999"; + var styleMenuLink = + "color:#fff; text-decoration: none; vertical-align: middle"; + var styleAppIcon = + "height: 1.5em; width: 1.5em; margin-right: 0.5em; border-radius:10%; vertical-align: middle"; + var title = document.getElementsByTagName("title")[0]; + if (typeof title == "undefined") { + title = document.createElement("title"); + document.getElementsByTagName("head")[0].append(title); + } + title.innerText = window.webxdc.selfAddr; + + if (window.webxdc.selfName === "device0") { + var root = document.createElement("section"); + root.innerHTML = + '
' + + '
webxdc dev tools
' + + 'Add Peer' + + ' | ' + + 'Reset' + + "
"; + var controlPanel = root.firstChild; + + function loadIcon(name) { + var tester = new Image(); + tester.onload = () => { + root.innerHTML = + ''; + controlPanel.insertBefore(root.firstChild, controlPanel.childNodes[1]); + + var pageIcon = document.createElement("link"); + pageIcon.rel = "icon"; + pageIcon.href = name; + document.head.append(pageIcon); + }; + tester.src = name; + } + loadIcon("icon.png"); + loadIcon("icon.jpg"); + + document.getElementsByTagName("body")[0].append(controlPanel); + } +}; + +window.addEventListener("load", window.alterXdcApp);