Commit Graph
100 Commits
Author SHA1 Message Date
Valere 6b5f396e1e Share the mapping from parameters to call view model options
The SDK stopped sending join notifications: it threaded callIntent into
createCallViewModel$ but not its pair sendNotificationType, which
enterRTCSession used to read for itself, so an explicit
?sendNotificationType=ring — or an intent that implies one — no longer
reached joinRTCSession.

The mechanism is worth fixing rather than the instance. The defaults on
CallViewModelOptions describe a standalone Element Call, so a widget
caller that misses a field gets standalone behaviour rather than an error,
and the SDK is only ever a widget. Give both callers one shared mapping so
they cannot drift, and cover the whole chain from URL to options in tests.

autoLeaveWhenOthersLeft and waitForCallPickup stay out of it: the view
model never read those from the parameters, so enabling them for the SDK
would be a change in its behaviour rather than a fix.
2026-09-02 19:26:33 +02:00
Valere 182be8676d Return the widget from initializeWidget instead of assigning a global
Element Call reached the widget API through a mutable module-level
binding, which every consumer imported directly. Nothing outside the app
shell needs it any more, so hand it back from initializeWidget and thread
it through: the initializer returns it, main passes it to App, and App
uses it to build the host bridge and to await the client the host is
lending us.

ClientContext's loadClient is now only about restoring or creating a
session of Element Call's own, since a widget's client arrives as a prop
like any other host's would.

Also fixes an early return added in the previous commit, which skipped
starting the analytics settings listener when a client was supplied. That
was harmless until now, but would have stopped analytics following the
user's choices in widget mode.

sdk/main.ts asked the host to close by hand; it now uses the bridge, which
also stops the transport as the app does.
2026-09-02 16:56:09 +02:00
Valere f6f47ede62 Let a host supply Element Call's client
ClientProvider found its own client: from the widget API, or by restoring
or creating a session. A host that embeds Element Call already has one,
and owns the user's session, so accept it as a prop and skip all of that.

A supplied client seeds the state synchronously, since there is no session
of ours to restore and so nothing to wait for.

Also guard the broadcast that shuts down other instances of the app. It
protects Element Call's own session and crypto stores, which is why it was
already skipped in widget mode — a host's client has the same property, so
without this an embedded Element Call would close down the user's other
tabs on mount.

Adds the first tests for ClientContext, covering both
2026-09-02 15:14:10 +02:00
Valere eb117249d1 Configure analytics explicitly instead of discovering it
PosthogAnalytics read its own configuration out of the environment on
first use: the URL parameters, config.json, and the widget global. An
embedded Element Call has none of those to offer, and analytics that
configure themselves cannot be switched off by a host that does its own
reporting.

Take an AnalyticsConfig through PosthogAnalytics.configure() instead,
called from the initializer once the config has loaded. Unconfigured
analytics stay off.

Note the two halves of that config are decided differently, and have to
be: where the credentials come from depends on the package, but who owns
the user's analytics identity depends on how Element Call is running,
since the full package can be used as a widget too.

Drop the widget check around cryptoVersion, which never did anything —
widget mode never initialises crypto, so getCrypto() is already undefined
there.

Move the tests covering which package reads which credential source onto
analyticsConfigFromEnvironment, where that decision now lives.
2026-09-02 15:05:12 +02:00
Valere f28d9e9b06 Stop asking whether Element Call is a widget
The remaining reads of the widget global were all asking one of two
different questions, so they get two different answers.

The app shell — the auth hooks, automatic guest registration, the group
call loader's diagnostic and the initial mute state — wants to know
whether Element Call was launched as a widget. That is a property of the
URL it was launched with, so expose the isWidget that computeUrlParams
already computed internally, documented as being for shell use only.

The call interface — whether to offer the profile settings tab — wants to
know something about its host, so it asks the bridge. A host that can
dismiss Element Call owns the user's account, so their profile is not ours
to edit; this reuses the close capability as a proxy, with a TODO
alongside the others.

ClientContext also takes supportsReactions from the bridge rather than
checking four widget capabilities itself, which removes widgetApi from
InitResult — a field that was always null outside widget mode.

Note this changes behaviour for a malformed widget URL: one carrying a
widget ID and parent URL but missing the room, user, device or base URL
would previously have fallen back to registering a guest user, and will
now not.
2026-09-02 15:04:24 +02:00
Valere bc6c232ef0 Move the state layer onto the host bridge
MuteStates, CallViewModel and LocalMember reached the host through the
widget global. None of them are React components, so they take the bridge
as an explicit parameter: a constructor argument for MuteStates, a field
on CallViewModelOptions, and one on createLocalMembership$'s props.

src/state no longer refers to the widget API.

The conditionals around it mostly disappear: nullHostBridge's observables
are NEVER, so there is nothing to guard, and a request carries its own
reply rather than needing the transport and the original event.

CallViewModelWidget.test.ts drove hangup by emitting on the mocked
widget's action emitter, so it now injects a bridge instead, and checks
that the request is acknowledged.
2026-09-02 13:23:01 +02:00
Valere 8e78ac76bd Drop ErrorView's widget prop in favour of the host bridge
ErrorView needed a widget only to decide whether to offer a close button
or a link home. That prop was threaded down through ErrorPage, RichError
and GroupCallErrorBoundary from seven call sites, to answer the single
question of whether the host can dismiss Element Call — which the host
bridge now answers directly through the presence of close().

Read the bridge from context in ErrorView and remove the prop, along with
the plumbing that carried it.

No functional change: the rendered output is unchanged, as the existing
snapshot confirms.

Move GroupCallView onto the host bridge

GroupCallView asked the widget API to keep it on screen, to close it, and
to tell it when a preloaded call should join. Route all three through the
host bridge and drop the widget prop.
2026-09-02 13:03:23 +02:00
Valere d8d70812b8 Read the shared room key from the parameters context
useRoomEncryptionSystem is used from GroupCallView, inside the part of
Element Call that will become the embeddable component, but it reached for
getUrlParams() via getKeyForRoom(). Extract the lookup into a helper taking
the room ID and password explicitly: the hook supplies them from the
parameters context, while getKeyForRoom keeps reading the URL for its one
remaining caller in the app shell.

No functional change.
2026-09-02 11:00:42 +02:00
Valere 6e44332fd1 Stop reading URL parameters from inside the call path
The view models reached for getUrlParams() — and so window.location —
from deep inside the call path: CallViewModel, MediaDevices, Publisher,
LocalMember and the footer view model. An embedded Element Call has no URL
of its own, so these values have to arrive as arguments instead.

Add the relevant options to CallViewModelOptions, to the MediaDevices and
Publisher constructors, to createLocalMembership$ and enterRTCSession, and
to createCallFooterViewModel. The remaining React consumers read the
context added in the previous commit. AppViewModel now takes its audio
output options too, moving that URL read out to main.tsx, where the app
shell can act as the adapter.

The new CallViewModelOptions fields are optional, defaulting to what the
URL parameters resolve to outside widget mode; the MediaDevices and
Publisher arguments are required, so that every construction site has to
be explicit.

useTheme.test.ts mocked the UrlParams module with a factory, so it needed
updating to mock the hook rather than getUrlParams.

No functional change.
2026-09-02 10:31:48 +02:00
Valere bc6015eff2 Supply Element Call's parameters through a context
Element Call's parameters come from its URL, which works while it owns
the page but leaves an embedder with nowhere to put them. Add a context
so they can be provided directly, falling back to parsing
window.location when no provider is present.

This also decouples the thirteen consumers from react-router: useUrlParams
called useLocation, so each of them required a router ancestor, which the
embedded build will not have. The standalone and widget builds keep their
URL-derived behaviour via useUrlParamsFromLocation, provided in App.

No functional change.
2026-09-02 09:58:34 +02:00
Valere 0dca5835f6 Route Element Call's DOM decoration through an injectable root element
Element Call writes its theme classes, background and layout attributes
straight onto document.body, and portals its modals there too. That is
only correct while it owns the page; embedded in a host application it
has to confine itself to the container it was mounted into.

Add a RootElementContext, defaulting to document.body so that the
standalone and widget builds are unaffected, and point the theme classes,
data-background, no-scroll-body and the fullscreen target at it.

Give the Modal and Toast portals an explicit container as well. Radix and
vaul both default to document.body, so without this every modal, drawer
and toast would render outside the container and lose the theme and
platform attributes set on it.

No functional change: the root element is document.body until an embedder
provides otherwise.
2026-09-01 18:54:27 +02:00
Valere 0a8c24bca8 Allow the config to be supplied by an embedder
Config.init() derives the location of config.json from window.location,
which only makes sense while Element Call owns the page. When it is
embedded in a host application the host owns the configuration, so add
Config.initWith() to accept it directly.

Share the defaulting and validation between both paths via resolveConfig(),
so that an injected config behaves identically to a fetched one, and mark
initialization as complete so that the init() calls already on the startup
path resolve immediately instead of fetching over the top of it.

initDefault() becomes initWith({}), which also stops it handing out a
shallow copy of DEFAULT_CONFIG whose nested objects were shared with the
module-level default.
2026-09-01 18:40:53 +02:00
Valere 7485b3d71f Use a dedicated i18next instance instead of the global singleton
Element Call configured the global i18next singleton. When Element Call
runs embedded in a host application rather than as its own page, that
singleton belongs to the host, so configuring it would clobber the
host's translations.

Create Element Call's own instance in utils/i18n.ts, configure it in the
initializer, and pass it to components via <I18nextProvider>. Drop
.use(initReactI18next) from the initializer: it registers the instance
as react-i18next's global default, which is the global we are trying to
avoid. Tests and stories keep using it, so that they do not need to wrap
every render in a provider.

Two modules imported `t` directly from "i18next" and so were bound to
the global instance: utils/errors.ts now calls i18n.t() on the instance
(reached at call time, since i18next only assigns `t` during init), and
QrCode uses useTranslation() like every other component.

No functional change.
2026-09-01 18:30:05 +02:00
Valere 9d5ca297e3 Remove well-known discovery 2026-08-05 16:12:41 +02:00
Valere e1bc4a096b fix: FF mirrored self video lose rounding 2026-07-31 12:20:32 +02:00
Valere ffb6b0a5f6 Use rtc transport action with widgets 2026-07-30 10:43:09 +02:00
Valere 53ae78c609 break ratio when tile bigger than screen 2026-07-27 17:25:56 +02:00
Valere b046c4b569 fix: Safari preview tile no video is 16px wide only 2026-07-27 15:43:35 +02:00
Valere 41d0933d4d playwright: New toast 2026-06-25 15:55:08 +02:00
Valere dc5b541b21 fix(voice): Default to earpiece for voice only call 2026-06-25 13:10:27 +02:00
Valere 62a3ec8319 review: cleanup removed redundant default properties 2026-06-05 12:50:25 +02:00
Valere a430e87104 review: add test for busy behavior for Mic and Video buttons 2026-06-05 12:48:04 +02:00
Valere acc5a440c7 review: remove unneeded early return 2026-06-05 12:35:21 +02:00
Valere dc03d9b358 fix: Initial unmute is reverted 2026-06-05 10:29:36 +02:00
Valere 55b2537129 fixup: Update snapshot with button busy state 2026-06-04 19:14:12 +02:00
Valere b5b238ffd3 storybook: Add storybook for audio/video busy 2026-06-04 19:13:27 +02:00
Valere 4606373e5b feat(mute): add syncing state and disable toggle during async mute 2026-06-04 18:55:31 +02:00
Valere 4353f09e54 fixup snapshots 2026-06-04 15:48:30 +02:00
Valere c05c523182 fixup: ensure dynamic keys are extracted by i18n parser 2026-06-04 15:30:59 +02:00
Valere e7d37f87bb feat(errors): Report livekit as such rather than like unknown errors 2026-06-04 11:54:42 +02:00
Valere 3799ea2b4e fixup: Missing translation 2026-05-28 14:43:42 +02:00
Valere 2bd16beccf Merge branch 'livekit' into valere/improve_error_message 2026-05-28 14:27:17 +02:00
Valere 6777adac54 Merge branch 'livekit' into valere/devx/run_dev_on_phone 2026-05-28 13:17:22 +02:00
Valere 1fafc3b1ba Merge branch 'livekit' into valere/devx/livekit_logs 2026-05-28 12:45:39 +02:00
Valere 2928e34ee2 test: use toHaveCount instead of count()).toBe 2026-05-28 11:56:29 +02:00
Valere d98e800077 bump playwright
update playwright container image
2026-05-28 11:32:18 +02:00
Valere 8b86c1914d bump playwright
update playwright container image
2026-05-28 11:31:38 +02:00
Valere e815277fa2 touch commit to unbock gh 2026-05-28 08:59:05 +02:00
Valere 810e263cbe review: better doc 2026-05-28 08:51:00 +02:00
Valere 70a0914017 eslint 2026-05-27 14:18:34 +02:00
Valere 8011f2579c test: Add test for new option
review: Remove uneeed {" "}
2026-05-27 14:03:31 +02:00
Valere 187665748b Merge branch 'livekit' into valere/devx/livekit_logs 2026-05-27 11:46:38 +02:00
Valere 265781ea5e fix(regression): control buttons should be hidden on mobile PIP 2026-05-20 13:08:51 +02:00
Valere 1865110492 reproduce bug internal #578
Regression: Controls are shown in Mobile PIP
2026-05-20 13:08:11 +02:00
Valere dfbb6c770e devx: Allow to run local dev EC on mobile in same wifi 2026-05-18 13:25:56 +02:00
Valere 408a38a7d6 bugfix: When starting muted, have to click twice to unmute in call 2026-05-12 15:46:38 +02:00
Valere f1b61a9f1c update developer settings tab snapshot 2026-05-11 18:07:40 +02:00
Valere 2ac221661b review: Use Initializer instead of creating a component 2026-05-11 17:43:34 +02:00
Valere 25ac565f8b Merge branch 'livekit' into valere/devx/livekit_logs 2026-05-11 17:28:07 +02:00
Valere d0bfef0682 Improve error messages for sfu auth problems 2026-05-07 16:21:30 +02:00
Valere e06f717156 playwrigth: make dismiss toast more reliable 2026-05-07 11:23:59 +02:00
Valere 09fc38930f fixup 2026-04-30 17:07:13 +02:00
Valere e79255e23b fix accept call with latest web 2026-04-30 16:47:52 +02:00
Valere 3bc8c0590d dev-tool: Add option to enable extended livekit logs 2026-04-24 18:27:05 +02:00
Valere 2f2bdfce5b fixup: wrong skip 2026-04-24 12:32:41 +02:00
Valere 82b7e243eb This test does not need to run on both FF and chrome 2026-04-24 12:10:24 +02:00
Valere 0d5c71bb4e fixup 2026-04-24 11:29:34 +02:00
Valere 0864b7d828 Merge branch 'livekit' into valere/devx/use_synapse_api_for_playwright 2026-04-24 11:25:10 +02:00
Valere ca305b9da1 fix: wrong usage of poll 2026-04-24 11:24:00 +02:00
Valere 6317a06472 review: quick cleaning 2026-04-24 11:21:14 +02:00
Valere 3699138eaa Dismiss the new invite confirmation modal 2026-04-23 15:36:46 +02:00
Valere a7788a2bfa Dismiss the new invite confirmation modal 2026-04-23 11:33:52 +02:00
Valere d9dc61a49d mark test as slow 2026-04-23 09:50:01 +02:00
Valere 84ac0cc47d mark test as slow 2026-04-22 20:56:42 +02:00
Valere e5c11eee13 fix bad assert for reconnect 2026-04-22 20:27:30 +02:00
Valere 6edc4084c6 adjust timeouts 2026-04-22 20:16:59 +02:00
Valere 6d70247acb this test should be slow (taking more than 30s) 2026-04-22 20:13:00 +02:00
Valere 21b78d6dad test something in the reconnect bug 2026-04-22 19:30:15 +02:00
Valere 4d8e4c66eb adjust time out 2026-04-22 19:30:02 +02:00
Valere 78e7adc4c6 remove redundant toBeVisible before click 2026-04-22 18:49:51 +02:00
Valere 5c4d6d0317 turn an assertion as retryable with expect.poll 2026-04-22 18:06:24 +02:00
Valere 1b9682869a adjust timeouts 2026-04-22 17:25:14 +02:00
Valere 854cfdfe1b adjust timeouts 2026-04-22 16:51:04 +02:00
Valere f355aa1ed7 adjust timeout 2026-04-22 15:40:06 +02:00
Valere 879526ce5e adjust flaky timeouts 2026-04-22 15:17:40 +02:00
Valere 5dd1c76d1c increase timeout of flaky test 2026-04-22 14:42:35 +02:00
Valere f9dc4d9b82 Update a tohaveCount() timeout that made a test flak 2 times 2026-04-22 14:14:54 +02:00
Valere 2d9802b6f5 Merge branch 'livekit' into valere/devx/use_synapse_api_for_playwright 2026-04-22 12:10:28 +02:00
Valere 55de3fd216 tests optimizations 2026-04-22 09:03:57 +02:00
Valere 0e2d2c6f72 try paralelize registrations? 2026-04-21 18:36:40 +02:00
Valere 754a42ffd1 increase default timeout on huddle test 2026-04-21 18:14:51 +02:00
Valere 701edd9522 fixup lint 2026-04-21 17:54:10 +02:00
Valere 0127040a8c Remove slow? no registration anymore 2026-04-21 17:51:42 +02:00
Valere 24e721f572 fix web stuck problem! go to / and not to #/login 2026-04-21 17:51:42 +02:00
Valere 4211405e7b Use synapse API to register instead doing via UI 2026-04-21 17:51:42 +02:00
Valere 84ed5943ea fix: using vars. instead of secrets. for NETLIFY_SITE_ID 2026-04-21 11:55:03 +02:00
Valere 8b4762fb29 review: use role locator instead of testID 2026-04-17 17:29:35 +02:00
Valere eeafd81d31 test: add a screenshare test 2026-04-17 14:58:10 +02:00
Valere 29ba0bdf13 make zizmore happy with exact version in comment 2026-04-15 14:13:58 +02:00
Valere 0350f57275 fixup: Looks like the upload & download action used are not compatible 2026-04-15 14:01:26 +02:00
Valere 7871877863 bump livekit server version 2026-04-15 11:27:13 +02:00
Valere 68518e323e Skip NotAllowed test until livekit allows to detect that again 2026-04-15 10:28:19 +02:00
Valere 07e3dc1ca4 Update livekit to 2.18.1
Fix mocking in a test
Also add "skipLibCheck": true in tsconfig
2026-04-13 10:02:54 +02:00
Valere dbeae383eb update livekit again 2026-04-13 09:35:08 +02:00
Valere 208184909e fixup: prettier 2026-04-10 10:08:57 +02:00
Valere aea5815dab Ensure we don't publish to any transport until our own transport is ok 2026-04-10 09:24:34 +02:00
Valere 0f14da7fbb devx: Use new action to netlify the playwright report as EW is doing 2026-04-09 16:33:26 +02:00
Valere 40dacd523b review: Move the all advertised/active down to the LocalMember
And let the local member use it properly to send membership event and publish media
2026-04-09 15:22:10 +02:00
Valere 08006d640a fix: Ensure to publish on the correct transport 2026-04-08 11:10:27 +02:00
Valere 58d377c3f7 test: Add publishing bug reproduction test 2026-04-08 11:06:05 +02:00