Files
element-call/src/lotus/lotusWidgetOrigin.ts
T
Lotus CIandClaude Opus 5.5 a93bd9d7d8
CI / Build embedded bundle (pull_request) Successful in 3m43s
CI / Publish to Gitea npm registry (pull_request) Skipped
feat(lotus): work when served from its own origin (cinny #43)
Two things tied EC to the host's origin:

- Widget message check: matrix-widget-api's strictOriginCheck compares
  ev.origin with THIS frame's origin, so on call.chat.lotusguild.org every
  message from chat.lotusguild.org would be dropped and calls would not
  start. restrictToHost() instead requires ev.source === window.parent and
  ev.origin === parentUrl's origin. Same-origin deployments keep working
  (the host origin is our own origin there), and it is stricter than
  before: the sender must also be our parent window.
- Soundboard: the host's blob: clip URL is origin-bound. io.lotus.inject_audio
  now accepts the clip's bytes (`audio`, ArrayBuffer, <= 8 MiB) and prefers
  them over `url`; hosts that only send `url` are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-26 23:24:32 -04:00

54 lines
1.9 KiB
TypeScript

/*
Copyright 2026 Lotus Guild
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
type MessageLike = Pick<MessageEvent, "source" | "origin">;
/** True when a message came from our parent window, at the host's origin. */
export function isFromHost(
ev: MessageLike,
parentWindow: Window,
parentOrigin: string,
): boolean {
return ev.source === parentWindow && ev.origin === parentOrigin;
}
interface InboundTransport {
handleMessage: (ev: MessageEvent) => void;
}
/**
* [cinny #43] Only handle widget messages sent by the host (our parent window)
* from the origin it was loaded at (`parentUrl`).
*
* matrix-widget-api's `strictOriginCheck` compares `ev.origin` with this
* frame's OWN origin, which only works while EC is served from the host's
* origin; served from its own origin (e.g. call.chat.lotusguild.org) every
* host message would be dropped. Turning the check off instead would let any
* window post toWidget actions (including io.lotus.*). This compares against
* the host's origin and also requires the sender to be our parent, so it is
* correct same-origin and cross-origin alike.
*
* Swaps the transport's `message` listener; safe whether or not the transport
* has started (`stop()` removes `handleMessage`, the guarded one after this).
*/
export function restrictToHost(
transport: unknown,
parentOrigin: string,
parentWindow: Window = window.parent,
inbound: Pick<Window, "addEventListener" | "removeEventListener"> = window,
): void {
const t = transport as InboundTransport;
const original = t.handleMessage;
const guarded = (ev: MessageEvent): void => {
if (!isFromHost(ev, parentWindow, parentOrigin)) return;
original(ev);
};
inbound.removeEventListener("message", original as EventListener);
t.handleMessage = guarded;
inbound.addEventListener("message", guarded as EventListener);
}