From a3e808341ad20fa62fa7f7b97457ec9ef3417ce9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 25 Oct 2026 20:23:17 -0400 Subject: [PATCH] Fix Sendspin Party with aiosendspin 10.2 Bump the web player's sendspin-js SDK from 3.0.1 to 5.0.0. aiosendspin 10.2 refuses 3.0.1 as an unencrypted legacy client. Load the SDK from jsDelivr's +esm bundle, because unpkg's ?module mode serves the CommonJS build of @noble/ciphers and the import fails. Bump the app.js cache buster so browsers fetch the new import. Give the party server a PartyPairingStore that approves every client for unpaired playback. aiosendspin activates no roles for an unpaired client that the operator did not approve, so connected browsers got no audio. Co-Authored-By: Claude Opus 5.5 --- sendspin/serve/__init__.py | 6 +++--- sendspin/serve/server.py | 12 ++++++++++++ sendspin/serve/web/app.js | 2 +- sendspin/serve/web/index.html | 2 +- sendspin/serve/worker.py | 6 +++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/sendspin/serve/__init__.py b/sendspin/serve/__init__.py index 81f05c8..db3dd0b 100644 --- a/sendspin/serve/__init__.py +++ b/sendspin/serve/__init__.py @@ -15,7 +15,7 @@ from typing import TYPE_CHECKING, Any import qrcode -from aiosendspin.noise import Identity, InMemoryServerPairingStore +from aiosendspin.noise import Identity from aiosendspin.server import ( ClientAddedEvent, ClientRemovedEvent, @@ -27,7 +27,7 @@ from sendspin.utils import create_task -from .server import SendspinPlayerServer +from .server import PartyPairingStore, SendspinPlayerServer from .source import AudioSource, decode_audio if TYPE_CHECKING: @@ -124,7 +124,7 @@ async def run_server(config: ServeConfig) -> int: loop=event_loop, identity=Identity.generate(), server_name=config.name, - pairing_store=InMemoryServerPairingStore(), + pairing_store=PartyPairingStore(), ) client_connected = asyncio.Event() diff --git a/sendspin/serve/server.py b/sendspin/serve/server.py index 9a3ef1d..62d3fba 100644 --- a/sendspin/serve/server.py +++ b/sendspin/serve/server.py @@ -8,9 +8,21 @@ from typing import Any from aiohttp import web +from aiosendspin.noise import InMemoryServerPairingStore, TrustedUnpairedClient from aiosendspin.server import SendspinServer +class PartyPairingStore(InMemoryServerPairingStore): + """Pairing store that approves every client for unpaired playback. + + A party admits anyone who opens the page, so no operator approves clients. + """ + + async def trusted_unpaired(self, client_id: str) -> TrustedUnpairedClient | None: + """Return the stored approval, or a new approval for any other client.""" + return await super().trusted_unpaired(client_id) or TrustedUnpairedClient(client_id) + + class SendspinPlayerServer(SendspinServer): """SendspinServer that serves an embedded web player at /.""" diff --git a/sendspin/serve/web/app.js b/sendspin/serve/web/app.js index 14352e6..05ce0f1 100644 --- a/sendspin/serve/web/app.js +++ b/sendspin/serve/web/app.js @@ -650,7 +650,7 @@ elements.listenToggleBtn.addEventListener("click", async () => { }); const sdkImport = import( - "https://unpkg.com/@sendspin/sendspin-js@3.0.1/dist/index.js?module", + "https://cdn.jsdelivr.net/npm/@sendspin/sendspin-js@5.0.0/+esm", ); // QR Code generation (using qrcode-generator loaded via script tag) diff --git a/sendspin/serve/web/index.html b/sendspin/serve/web/index.html index b246867..aeccb01 100644 --- a/sendspin/serve/web/index.html +++ b/sendspin/serve/web/index.html @@ -99,6 +99,6 @@

About Sendspin

- + diff --git a/sendspin/serve/worker.py b/sendspin/serve/worker.py index 8d7bc04..620a685 100644 --- a/sendspin/serve/worker.py +++ b/sendspin/serve/worker.py @@ -12,7 +12,7 @@ from multiprocessing.sharedctypes import Synchronized from contextlib import suppress -from aiosendspin.noise import Identity, InMemoryServerPairingStore +from aiosendspin.noise import Identity from aiosendspin.server import ( ClientAddedEvent, ClientRemovedEvent, @@ -31,7 +31,7 @@ WorkerError, WorkerListening, ) -from sendspin.serve.server import SendspinPlayerServer +from sendspin.serve.server import PartyPairingStore, SendspinPlayerServer from sendspin.utils import create_task logger = logging.getLogger(__name__) @@ -105,7 +105,7 @@ async def _start_server(self) -> None: loop=loop, identity=Identity.generate(), server_name=f"Sendspin Worker {self.worker_id}", - pairing_store=InMemoryServerPairingStore(), + pairing_store=PartyPairingStore(), total_listeners=self._total_listeners, ) self._server.add_event_listener(self._on_server_event)