Browse Source

fix proxy

imwald
Silberengel 1 month ago
parent
commit
7f7dcb6ded
  1. 3
      Dockerfile
  2. 22
      PROXY_SETUP.md
  3. 6
      docker-compose.prod.yml
  4. 5
      scripts/build-and-push-prod.sh
  5. 5
      src/constants.ts
  6. 32
      src/lib/read-aloud.ts
  7. 5
      vite.config.ts

3
Dockerfile

@ -4,6 +4,9 @@ FROM node:20-alpine AS builder
ARG VITE_PROXY_SERVER ARG VITE_PROXY_SERVER
ENV VITE_PROXY_SERVER=${VITE_PROXY_SERVER} ENV VITE_PROXY_SERVER=${VITE_PROXY_SERVER}
ARG VITE_READ_ALOUD_TTS_URL
ENV VITE_READ_ALOUD_TTS_URL=${VITE_READ_ALOUD_TTS_URL}
WORKDIR /app WORKDIR /app
# Copy package files first # Copy package files first

22
PROXY_SETUP.md

@ -97,6 +97,28 @@ docker-compose up -d
- What URL is being used to fetch metadata - What URL is being used to fetch metadata
- Any errors (CORS, network, etc.) - Any errors (CORS, network, etc.)
## Read-aloud / Piper TTS (same-origin `/api/piper-tts`)
The client uses **`POST /api/piper-tts`** on the **same host** as the app (default build: `VITE_READ_ALOUD_TTS_URL=/api/piper-tts`) so the browser does not need cross-origin CORS to aitherboard.
Add these **before** the catch-all `ProxyPass /` to the Jumble static container (same ordering as `/sites/`):
```apache
ProxyPass /api/piper-tts http://127.0.0.1:9876/api/piper-tts
ProxyPassReverse /api/piper-tts http://127.0.0.1:9876/api/piper-tts
```
Use the port where **aitherboard** listens (example: `9876`). Reload Apache, then test:
```bash
curl -sS -o /tmp/t.wav -w "%{http_code}\n" -H "Content-Type: application/json" \
-d '{"text":"test","speed":1}' "https://jumble.imwald.eu/api/piper-tts"
```
Expect **200** and a WAV file. **Local dev:** `npm run dev` proxies `/api/piper-tts``http://127.0.0.1:9876` in `vite.config.ts`.
Rebuild the Jumble image after changing `VITE_READ_ALOUD_TTS_URL`; `Dockerfile` passes `ARG`/`ENV` `VITE_READ_ALOUD_TTS_URL` into `npm run build`.
## Update Proxy Server's ALLOW_ORIGIN ## Update Proxy Server's ALLOW_ORIGIN
Since users access via `https://jumble.imwald.eu`, you need to update the proxy server's `ALLOW_ORIGIN`: Since users access via `https://jumble.imwald.eu`, you need to update the proxy server's `ALLOW_ORIGIN`:

6
docker-compose.prod.yml

@ -1,10 +1,12 @@
# Minimal compose for running the published image (e.g. on remote server). # Minimal compose for running the published image (e.g. on remote server).
# Usage: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d # Usage: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d
# #
# Apache (unchanged on your host) should keep: # Apache (unchanged on your host) should keep (order: specific paths before catch-all /):
# ProxyPass /sites/ http://127.0.0.1:8090/sites/ # ProxyPass /sites/ http://127.0.0.1:8090/sites/
# ProxyPass /api/piper-tts http://127.0.0.1:9876/api/piper-tts
# ProxyPassReverse /api/piper-tts http://127.0.0.1:9876/api/piper-tts
# ProxyPass / http://127.0.0.1:8089/ # ProxyPass / http://127.0.0.1:8089/
# so the browser hits https://<host>/sites/?url=… on Apache → OG proxy (8090); static SPA is 8089 only. # so the browser hits same-origin /api/piper-tts → aitherboard; /sites/ → OG proxy; else static SPA on 8089.
# VITE_PROXY_SERVER / VITE_READ_ALOUD_TTS_URL are baked at image build — see scripts/build-and-push-prod.sh # VITE_PROXY_SERVER / VITE_READ_ALOUD_TTS_URL are baked at image build — see scripts/build-and-push-prod.sh
# #
# NIP-66 monitor: set NIP66_MONITOR_NSEC (and optionally NIP66_MONITOR_NPUB) in the host env or .env. # NIP-66 monitor: set NIP66_MONITOR_NSEC (and optionally NIP66_MONITOR_NPUB) in the host env or .env.

5
scripts/build-and-push-prod.sh

@ -6,7 +6,8 @@
# Optional env: # Optional env:
# JUMBLE_PROXY_SERVER_URL — build-arg VITE_PROXY_SERVER (default https://jumble.imwald.eu). # JUMBLE_PROXY_SERVER_URL — build-arg VITE_PROXY_SERVER (default https://jumble.imwald.eu).
# Must match the public origin where Apache serves the app; Apache proxies /sites/ → :8090, not this container. # Must match the public origin where Apache serves the app; Apache proxies /sites/ → :8090, not this container.
# READ_ALOUD_TTS_URL — build-arg VITE_READ_ALOUD_TTS_URL (default https://aitherboard.imwald.eu/api/piper-tts). # READ_ALOUD_TTS_URL — build-arg VITE_READ_ALOUD_TTS_URL (default /api/piper-tts).
# Same-origin: Apache proxies /api/piper-tts → aitherboard (e.g. :9876). Override only if you use CORS on another host.
set -e set -e
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
@ -21,7 +22,7 @@ IMAGE_MONITOR="silberengel/imwald-jumble-nip66-monitor"
# Use public origin only (no /proxy path): web.service builds <origin>/sites/?url=… # Use public origin only (no /proxy path): web.service builds <origin>/sites/?url=…
# Override: JUMBLE_PROXY_SERVER_URL=https://other.example ./scripts/build-and-push-prod.sh # Override: JUMBLE_PROXY_SERVER_URL=https://other.example ./scripts/build-and-push-prod.sh
JUMBLE_PROXY_SERVER_URL="${JUMBLE_PROXY_SERVER_URL:-https://jumble.imwald.eu}" JUMBLE_PROXY_SERVER_URL="${JUMBLE_PROXY_SERVER_URL:-https://jumble.imwald.eu}"
READ_ALOUD_TTS_URL="${READ_ALOUD_TTS_URL:-https://aitherboard.imwald.eu/api/piper-tts}" READ_ALOUD_TTS_URL="${READ_ALOUD_TTS_URL:-/api/piper-tts}"
echo "Building main app (version: $VERSION, VITE_PROXY_SERVER=$JUMBLE_PROXY_SERVER_URL, VITE_READ_ALOUD_TTS_URL=$READ_ALOUD_TTS_URL)" echo "Building main app (version: $VERSION, VITE_PROXY_SERVER=$JUMBLE_PROXY_SERVER_URL, VITE_READ_ALOUD_TTS_URL=$READ_ALOUD_TTS_URL)"
docker build \ docker build \

5
src/constants.ts

@ -12,8 +12,9 @@ export const GITREPUBLIC_WEB_BASE_URL = (
.replace(/\/$/, '') .replace(/\/$/, '')
/** /**
* Piper TTS proxy (same contract as aitherboard `POST /api/piper-tts`: JSON `{ text, voice?, speed? }`, body `audio/wav`). * Piper TTS (same contract as aitherboard `POST /api/piper-tts`: JSON `{ text, voice?, speed? }`, body `audio/wav`).
* Set `VITE_READ_ALOUD_TTS_URL` to your deployed aitherboard URL, e.g. `https://aitherboard.example.com/api/piper-tts`. * Default production: `/api/piper-tts` (same origin; reverse-proxy to aitherboard see PROXY_SETUP.md).
* For cross-origin aitherboard instead, set full URL and configure CORS on that host.
* If empty, read-aloud uses the Web Speech API only. * If empty, read-aloud uses the Web Speech API only.
*/ */
export const READ_ALOUD_TTS_URL = export const READ_ALOUD_TTS_URL =

32
src/lib/read-aloud.ts

@ -1,7 +1,19 @@
import { ExtendedKind, READ_ALOUD_TTS_URL } from '@/constants' import { ExtendedKind, READ_ALOUD_TTS_URL } from '@/constants'
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata' import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
import logger from '@/lib/logger'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
function readAloudEndpointForLog(): string {
const u = READ_ALOUD_TTS_URL
if (!u) return ''
try {
const parsed = new URL(u)
return `${parsed.origin}${parsed.pathname}`
} catch {
return u.length > 96 ? `${u.slice(0, 96)}` : u
}
}
export type ReadAloudResult = 'ok' | 'unsupported' | 'empty' | 'error' export type ReadAloudResult = 'ok' | 'unsupported' | 'empty' | 'error'
const KINDS_WITH_METADATA_TITLE = new Set<number>([ const KINDS_WITH_METADATA_TITLE = new Set<number>([
@ -77,11 +89,16 @@ async function speakViaPiperTts(text: string): Promise<ReadAloudResult> {
}) })
if (!response.ok) { if (!response.ok) {
logger.warn('[ReadAloud] Piper HTTP error', {
status: response.status,
endpoint: readAloudEndpointForLog()
})
return 'error' return 'error'
} }
const blob = await response.blob() const blob = await response.blob()
if (!blob.size) { if (!blob.size) {
logger.warn('[ReadAloud] Piper returned empty body', { endpoint: readAloudEndpointForLog() })
return 'error' return 'error'
} }
@ -109,7 +126,11 @@ async function speakViaPiperTts(text: string): Promise<ReadAloudResult> {
try { try {
await audio.play() await audio.play()
return 'ok' return 'ok'
} catch { } catch (playErr) {
logger.warn('[ReadAloud] Piper audio.play() blocked or failed', {
endpoint: readAloudEndpointForLog(),
error: playErr instanceof Error ? playErr.message : String(playErr)
})
cleanupBlob() cleanupBlob()
if (readAloudAudio === audio) { if (readAloudAudio === audio) {
readAloudAudio = null readAloudAudio = null
@ -123,6 +144,10 @@ async function speakViaPiperTts(text: string): Promise<ReadAloudResult> {
if (isAbort) { if (isAbort) {
return 'ok' return 'ok'
} }
logger.warn('[ReadAloud] Piper fetch failed (check CORS on the TTS host or use same-origin /api/piper-tts)', {
endpoint: readAloudEndpointForLog(),
error: e instanceof Error ? e.message : String(e)
})
return 'error' return 'error'
} }
} }
@ -147,7 +172,10 @@ export async function speakNoteReadAloud(event: Event): Promise<ReadAloudResult>
if (piperResult === 'ok') { if (piperResult === 'ok') {
return 'ok' return 'ok'
} }
// Server failed or unreachable: fall back to Web Speech when available logger.warn(
'[ReadAloud] Using Web Speech fallback — Piper did not play. See previous [ReadAloud] log for cause.',
{ endpoint: readAloudEndpointForLog() }
)
} }
if (!window.speechSynthesis) { if (!window.speechSynthesis) {

5
vite.config.ts

@ -102,6 +102,11 @@ export default defineConfig(({ mode }) => {
// OG/link preview uses `/sites/?url=…`. Without this, Vite serves `index.html` and WebService parses the app shell. // OG/link preview uses `/sites/?url=…`. Without this, Vite serves `index.html` and WebService parses the app shell.
// Run the scraper on 8090 per PROXY_SETUP.md, or rely on allorigins fallback in dev (web.service.ts). // Run the scraper on 8090 per PROXY_SETUP.md, or rely on allorigins fallback in dev (web.service.ts).
proxy: { proxy: {
// Read-aloud Piper: same path as production Apache → aitherboard (avoid cross-origin CORS in dev).
'/api/piper-tts': {
target: 'http://127.0.0.1:9876',
changeOrigin: true
},
'/sites': { '/sites': {
target: 'http://127.0.0.1:8090', target: 'http://127.0.0.1:8090',
changeOrigin: true changeOrigin: true

Loading…
Cancel
Save