From 8568969b71171014e50b6c87731a0b1874ecc530 Mon Sep 17 00:00:00 2001 From: Septias Date: Wed, 3 Apr 2024 20:02:40 +0200 Subject: [PATCH 1/5] adjust for ephemeral channels --- webxdc.d.ts | 14 ++++++++++++++ webxdc.js | 25 +++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/webxdc.d.ts b/webxdc.d.ts index e5d4ef8..caa0698 100644 --- a/webxdc.d.ts +++ b/webxdc.d.ts @@ -82,6 +82,13 @@ interface Webxdc { cb: (statusUpdate: ReceivedStatusUpdate) => void, serial?: number ): Promise; + + /** + * Set a listener for _ephemeral_ status updates. + * Own status updates are not received. + */ + setEphemeralUpdateListener(cb: (payload: T) => void): void; + /** * @deprecated See {@link setUpdateListener|`setUpdateListener()`}. */ @@ -92,6 +99,13 @@ interface Webxdc { * @param description short, human-readable description what this update is about. this is shown eg. as a fallback text in an email program. */ sendUpdate(update: SendingStatusUpdate, description: string): void; + + /** + * Send an ephemeral update to another peer. + * @param payload Data that can be serialized with `JSON.stringify`. + */ + sendEphemeralUpdate(payload: T): void; + /** * Send a message with file, text or both to a chat. * Asks user to what Chat to send the message to. diff --git a/webxdc.js b/webxdc.js index 8f68150..2462a6d 100644 --- a/webxdc.js +++ b/webxdc.js @@ -10,7 +10,9 @@ /** @type {import('./webxdc').Webxdc} */ window.webxdc = (() => { var updateListener = (_) => {}; + var ephemeralUpdatListener = (_) => {}; var updatesKey = "__xdcUpdatesKey__"; + let ephemeralUpdateKey = "__xdcEphemeralUpdateKey__"; window.addEventListener("storage", (event) => { if (event.key == null) { window.location.reload(); @@ -20,6 +22,11 @@ window.webxdc = (() => { update.max_serial = updates.length; console.log("[Webxdc] " + JSON.stringify(update)); updateListener(update); + } else if (event.key === ephemeralUpdateKey) { + var updates = JSON.parse(event.newValue); + var update = updates[updates.length - 1]; + console.log("[Webxdc] " + JSON.stringify(update)); + ephemeralUpdatListener(update); } }); @@ -28,6 +35,11 @@ window.webxdc = (() => { return updatesJSON ? JSON.parse(updatesJSON) : []; } + function getEphemeralUpdate() { + var ephemeralUpdateJSON = window.localStorage.getItem(ephemeralUpdateKey); + return ephemeralUpdateJSON ? JSON.parse(ephemeralUpdateJSON) : []; + } + var params = new URLSearchParams(window.location.hash.substr(1)); return { selfAddr: params.get("addr") || "device0@local.host", @@ -44,6 +56,10 @@ window.webxdc = (() => { updateListener = cb; return Promise.resolve(); }, + setEphemeralUpdateListener: (cb) => { + localStorage.setItem(ephemeralUpdateKey, JSON.stringify([])); + ephemeralUpdatListener = cb; + }, getAllUpdates: () => { console.log("[Webxdc] WARNING: getAllUpdates() is deprecated."); return Promise.resolve([]); @@ -66,6 +82,11 @@ window.webxdc = (() => { ); updateListener(_update); }, + sendEphemeralUpdate: (payload) => { + let updates = getEphemeralUpdate(); + updates.push(payload); + window.localStorage.setItem(ephemeralUpdateKey, JSON.stringify(updates)); + }, sendToChat: async (content) => { if (!content.file && !content.text) { alert("🚨 Error: either file or text need to be set. (or both)"); @@ -245,8 +266,8 @@ window.alterXdcApp = () => { root.innerHTML = ''; controlPanel.insertBefore(root.firstChild, controlPanel.childNodes[1]); - - var pageIcon = document.createElement('link'); + + var pageIcon = document.createElement("link"); pageIcon.rel = "icon"; pageIcon.href = name; document.head.append(pageIcon); From a1eb565a4d1452666dc2970019ab5ca2a9fe1a86 Mon Sep 17 00:00:00 2001 From: Septias Date: Wed, 3 Apr 2024 20:04:58 +0200 Subject: [PATCH 2/5] remove log --- webxdc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webxdc.js b/webxdc.js index 2462a6d..78b1499 100644 --- a/webxdc.js +++ b/webxdc.js @@ -25,7 +25,6 @@ window.webxdc = (() => { } else if (event.key === ephemeralUpdateKey) { var updates = JSON.parse(event.newValue); var update = updates[updates.length - 1]; - console.log("[Webxdc] " + JSON.stringify(update)); ephemeralUpdatListener(update); } }); From f437ce27a11fe894dd705238e3a0fff51dec59a1 Mon Sep 17 00:00:00 2001 From: Septias Date: Mon, 8 Apr 2024 13:20:50 +0200 Subject: [PATCH 3/5] only use one message --- webxdc.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/webxdc.js b/webxdc.js index 78b1499..a7ab86b 100644 --- a/webxdc.js +++ b/webxdc.js @@ -22,10 +22,14 @@ window.webxdc = (() => { update.max_serial = updates.length; console.log("[Webxdc] " + JSON.stringify(update)); updateListener(update); - } else if (event.key === ephemeralUpdateKey) { - var updates = JSON.parse(event.newValue); - var update = updates[updates.length - 1]; - ephemeralUpdatListener(update); + } + // Tab-communication could also use https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage + // The local storage approach should be supported on more older browsers. + else if (event.key === ephemeralUpdateKey) { + var [sender, update] = JSON.parse(event.newValue); + if (window.webxdc.selfAddr !== sender) { + ephemeralUpdatListener(update); + } } }); @@ -56,7 +60,6 @@ window.webxdc = (() => { return Promise.resolve(); }, setEphemeralUpdateListener: (cb) => { - localStorage.setItem(ephemeralUpdateKey, JSON.stringify([])); ephemeralUpdatListener = cb; }, getAllUpdates: () => { @@ -83,8 +86,10 @@ window.webxdc = (() => { }, sendEphemeralUpdate: (payload) => { let updates = getEphemeralUpdate(); - updates.push(payload); - window.localStorage.setItem(ephemeralUpdateKey, JSON.stringify(updates)); + window.localStorage.setItem( + ephemeralUpdateKey, + JSON.stringify([window.webxdc.selfAddr, payload]) + ); }, sendToChat: async (content) => { if (!content.file && !content.text) { From 30a3c2b43e009421567506906f45ee09a12ddaec Mon Sep 17 00:00:00 2001 From: Septias Date: Mon, 8 Apr 2024 18:13:31 +0200 Subject: [PATCH 4/5] async sendEphemeralUpdate --- webxdc.d.ts | 2 +- webxdc.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/webxdc.d.ts b/webxdc.d.ts index caa0698..76c7368 100644 --- a/webxdc.d.ts +++ b/webxdc.d.ts @@ -87,7 +87,7 @@ interface Webxdc { * Set a listener for _ephemeral_ status updates. * Own status updates are not received. */ - setEphemeralUpdateListener(cb: (payload: T) => void): void; + setEphemeralUpdateListener(cb: (payload: T) => void): Promise; /** * @deprecated See {@link setUpdateListener|`setUpdateListener()`}. diff --git a/webxdc.js b/webxdc.js index a7ab86b..71ca30c 100644 --- a/webxdc.js +++ b/webxdc.js @@ -61,6 +61,7 @@ window.webxdc = (() => { }, setEphemeralUpdateListener: (cb) => { ephemeralUpdatListener = cb; + return Promise.resolve(); }, getAllUpdates: () => { console.log("[Webxdc] WARNING: getAllUpdates() is deprecated."); From 6179446de8db1b44af8904e11c787594bae759bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kl=C3=A4hn?= Date: Sun, 12 May 2024 16:04:14 +0200 Subject: [PATCH 5/5] update to specs --- webxdc.d.ts | 31 +++++++++++++------- webxdc.js | 81 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/webxdc.d.ts b/webxdc.d.ts index 76c7368..fa7c2a2 100644 --- a/webxdc.d.ts +++ b/webxdc.d.ts @@ -65,6 +65,25 @@ type SendOptions = text: string; }; +/** + * A listener for realtime data. + */ +export class RealtimeListener { + private listener: (data: Uint8Array) => void + private trashed: boolean + + /* Whether the realtime channel was left */ + is_trashed(): boolean + /* Receive data from the realtime channel */ + receive(data: Uint8Array): void + /* Set a listener for the realtime channel */ + setListener(listener: (data: Uint8Array) => void): void + /* Send data over the realtime channel */ + send(data: Uint8Array): void + /* Leave the realtime channel */ + leave(): void +} + interface Webxdc { /** Returns the peer's own address. * This is esp. useful if you want to differ between different peers - just send the address along with the payload, @@ -84,10 +103,9 @@ interface Webxdc { ): Promise; /** - * Set a listener for _ephemeral_ status updates. - * Own status updates are not received. + * Join a realtime channel. */ - setEphemeralUpdateListener(cb: (payload: T) => void): Promise; + joinRealtimeChannel(): RealtimeListener; /** * @deprecated See {@link setUpdateListener|`setUpdateListener()`}. @@ -99,13 +117,6 @@ interface Webxdc { * @param description short, human-readable description what this update is about. this is shown eg. as a fallback text in an email program. */ sendUpdate(update: SendingStatusUpdate, description: string): void; - - /** - * Send an ephemeral update to another peer. - * @param payload Data that can be serialized with `JSON.stringify`. - */ - sendEphemeralUpdate(payload: T): void; - /** * Send a message with file, text or both to a chat. * Asks user to what Chat to send the message to. diff --git a/webxdc.js b/webxdc.js index 71ca30c..8fb5ee7 100644 --- a/webxdc.js +++ b/webxdc.js @@ -4,15 +4,60 @@ // 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 = (_) => {}; - var ephemeralUpdatListener = (_) => {}; + /** + * @type {RT | null} + */ + var realtimeListener = null; var updatesKey = "__xdcUpdatesKey__"; - let ephemeralUpdateKey = "__xdcEphemeralUpdateKey__"; window.addEventListener("storage", (event) => { if (event.key == null) { window.location.reload(); @@ -22,13 +67,10 @@ window.webxdc = (() => { update.max_serial = updates.length; console.log("[Webxdc] " + JSON.stringify(update)); updateListener(update); - } - // Tab-communication could also use https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage - // The local storage approach should be supported on more older browsers. - else if (event.key === ephemeralUpdateKey) { + } else if (event.key === ephemeralUpdateKey) { var [sender, update] = JSON.parse(event.newValue); - if (window.webxdc.selfAddr !== sender) { - ephemeralUpdatListener(update); + if (window.webxdc.selfAddr !== sender && realtimeListener && !realtimeListener.is_trashed()) { + realtimeListener.receive( Uint8Array.from(update)); } } }); @@ -38,11 +80,6 @@ window.webxdc = (() => { return updatesJSON ? JSON.parse(updatesJSON) : []; } - function getEphemeralUpdate() { - var ephemeralUpdateJSON = window.localStorage.getItem(ephemeralUpdateKey); - return ephemeralUpdateJSON ? JSON.parse(ephemeralUpdateJSON) : []; - } - var params = new URLSearchParams(window.location.hash.substr(1)); return { selfAddr: params.get("addr") || "device0@local.host", @@ -59,9 +96,14 @@ window.webxdc = (() => { updateListener = cb; return Promise.resolve(); }, - setEphemeralUpdateListener: (cb) => { - ephemeralUpdatListener = 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."); @@ -85,13 +127,6 @@ window.webxdc = (() => { ); updateListener(_update); }, - sendEphemeralUpdate: (payload) => { - let updates = getEphemeralUpdate(); - window.localStorage.setItem( - ephemeralUpdateKey, - JSON.stringify([window.webxdc.selfAddr, payload]) - ); - }, sendToChat: async (content) => { if (!content.file && !content.text) { alert("🚨 Error: either file or text need to be set. (or both)");