Fix recording access not working in restrictive browsers#2901
Fix recording access not working in restrictive browsers#2901
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughRefactors recording-format access from API/window.open flows to web-route navigation and cross-window messaging, renames four CustomErrorMessages enum cases and matching JS constants (removing ROOM_/FILE_ prefixes), adds a 404 view that posts messages to window.opener, and updates controllers, routes, frontend components, and tests accordingly. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can get early access to new features in CodeRabbit.Enable the |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2901 +/- ##
==========================================
Coverage 96.71% 96.71%
- Complexity 1906 1907 +1
==========================================
Files 445 445
Lines 12878 12895 +17
Branches 2072 2069 -3
==========================================
+ Hits 12455 12472 +17
Misses 423 423 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
PILOS
|
||||||||||||||||||||||||||||
| Project |
PILOS
|
| Branch Review |
2851-recording-access-not-working-in-restrictive-browsers
|
| Run status |
|
| Run duration | 07m 37s |
| Commit |
|
| Committer | Sabrina Wüst |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
621
|
| View all changes introduced in this branch ↗︎ | |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
app/Http/Controllers/RecordingFormatController.php (1)
30-30: Usesession()->putinstead ofpushfor a boolean access flag.At Line 30,
pushgrows an array entry on every access, butRecordingController::resourceonly checks key existence.putavoids unnecessary session growth.Proposed refactor
- session()->push('access-format-'.$format->id, true); + session()->put('access-format-'.$format->id, true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/Http/Controllers/RecordingFormatController.php` at line 30, Replace the session array append call with a boolean put: in RecordingFormatController where session()->push('access-format-'.$format->id, true) is used, change it to session()->put('access-format-'.$format->id, true) so the session stores a single boolean flag (RecordingController::resource checks key existence) and avoids growing an array on every access.tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js (1)
28-67: Consolidate repeated format-link assertions into a helper.The same 4-link assertion block is duplicated multiple times, which increases maintenance cost and drift risk.
♻️ Example Cypress helper pattern
+ const assertFormatLink = ({ selector, label, href, rel = "opener" }) => { + cy.get(selector) + .should("be.visible") + .and("include.text", label) + .and("have.attr", "href", href) + .and("have.attr", "rel", rel) + .and("have.attr", "target", "_blank"); + };Also applies to: 132-171, 352-391
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js` around lines 28 - 67, The test repeats four identical assertion blocks for format links (selectors '[data-test="notes-button"]', '[data-test="podcast-button"]', '[data-test="presentation-button"]', '[data-test="screenshare-button"]') in RoomsViewRecordingsRecordingActions.cy.js; extract a reusable helper (e.g., assertFormatLink or checkFormatLink) that accepts the button selector, the expected i18n text key (like "rooms.recordings.format_types.notes"), and the expected href suffix (formats/1 .. formats/4) and replace each duplicated block with calls to that helper; ensure the helper performs the same checks (.should("be.visible"), .and("include.text", ...), .and("have.attr","href", ...), .and("have.attr","rel","opener"), .and("have.attr","target","_blank")) so you can reuse it wherever the same pattern appears (also at the other ranges noted).tests/Backend/Feature/api/v1/RecordingTest.php (1)
535-655: Extract shared helpers to reduce repetitive route/error assertions.The repeated route param arrays and
new-tab-errorassertion blocks make updates error-prone and noisy.♻️ Example helper extraction
+ private function formatShowRouteParams(Recording $recording, RecordingFormat $format, array $extra = []): array + { + return array_merge([ + 'room' => $recording->room->id, + 'recording' => $recording->id, + 'format' => $format->id, + ], $extra); + } + + private function assertNewTabError($response, string $type, int $code, string $title, string $message): void + { + $response->assertStatus($code) + ->assertViewIs('new-tab-error') + ->assertViewHasAll(compact('type', 'code', 'title', 'message')); + }Also applies to: 679-697, 761-805, 842-849, 878-885, 936-955, 972-1007
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Backend/Feature/api/v1/RecordingTest.php` around lines 535 - 655, The test repeats building the route parameter arrays and asserting the same "new-tab-error" view responses and redirects; extract reusable helpers in the test class to reduce duplication and make changes safe. Add a helper like requestFormatShow(array $params, array $cookies = []) that builds the route('rooms.recordings.formats.show', $params) and performs $this->get() (using withCookies when $cookies supplied), a helper assertNewTabError(array $expected) that calls ->assertForbidden()/->assertUnauthorized() as appropriate then ->assertViewIs('new-tab-error')->assertViewHasAll($expected), and a helper assertRecordingResourceRedirect($format, $recording, $resource) that wraps ->assertRedirectToRoute('recording.resource', [...]); then replace repeated blocks that reference route('rooms.recordings.formats.show'), ->assertViewIs('new-tab-error'), ->assertViewHasAll([...]), $currentSession, $roomAuthToken, $recording, and $format with these helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Http/Controllers/RecordingFormatController.php`:
- Around line 38-41: The code assumes explode($recording->id.'/', $format->url,
2)[1] always exists causing a potential undefined offset; update the parsing in
the RecordingFormatController to safely extract $resource from $format->url
(e.g., use explode with limit and check isset on index 1 or use
strpos/substring) and handle the missing case by returning a controlled response
(abort/404 or empty resource handling) before building $resourceRoute and
calling route('recording.resource').
In `@resources/js/components/RoomTabRecordingsViewButton.vue`:
- Around line 57-59: In RoomTabRecordingsViewButton.vue update the anchor tag
used in the view action (the element that calls viewFormatUrl(format)) to
replace rel="opener" with rel="noopener noreferrer" so external redirect targets
cannot access window.opener; locate the anchor that sets
:href="viewFormatUrl(format)" and change its rel attribute accordingly.
---
Nitpick comments:
In `@app/Http/Controllers/RecordingFormatController.php`:
- Line 30: Replace the session array append call with a boolean put: in
RecordingFormatController where session()->push('access-format-'.$format->id,
true) is used, change it to session()->put('access-format-'.$format->id, true)
so the session stores a single boolean flag (RecordingController::resource
checks key existence) and avoids growing an array on every access.
In `@tests/Backend/Feature/api/v1/RecordingTest.php`:
- Around line 535-655: The test repeats building the route parameter arrays and
asserting the same "new-tab-error" view responses and redirects; extract
reusable helpers in the test class to reduce duplication and make changes safe.
Add a helper like requestFormatShow(array $params, array $cookies = []) that
builds the route('rooms.recordings.formats.show', $params) and performs
$this->get() (using withCookies when $cookies supplied), a helper
assertNewTabError(array $expected) that calls
->assertForbidden()/->assertUnauthorized() as appropriate then
->assertViewIs('new-tab-error')->assertViewHasAll($expected), and a helper
assertRecordingResourceRedirect($format, $recording, $resource) that wraps
->assertRedirectToRoute('recording.resource', [...]); then replace repeated
blocks that reference route('rooms.recordings.formats.show'),
->assertViewIs('new-tab-error'), ->assertViewHasAll([...]), $currentSession,
$roomAuthToken, $recording, and $format with these helpers.
In `@tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js`:
- Around line 28-67: The test repeats four identical assertion blocks for format
links (selectors '[data-test="notes-button"]', '[data-test="podcast-button"]',
'[data-test="presentation-button"]', '[data-test="screenshare-button"]') in
RoomsViewRecordingsRecordingActions.cy.js; extract a reusable helper (e.g.,
assertFormatLink or checkFormatLink) that accepts the button selector, the
expected i18n text key (like "rooms.recordings.format_types.notes"), and the
expected href suffix (formats/1 .. formats/4) and replace each duplicated block
with calls to that helper; ensure the helper performs the same checks
(.should("be.visible"), .and("include.text", ...), .and("have.attr","href",
...), .and("have.attr","rel","opener"), .and("have.attr","target","_blank")) so
you can reuse it wherever the same pattern appears (also at the other ranges
noted).
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
app/Enums/CustomErrorMessages.phpapp/Http/Controllers/RecordingFormatController.phpapp/Http/Controllers/RoomFileController.phpapp/Http/Controllers/api/v1/RecordingFormatController.phpapp/Http/Controllers/api/v1/RoomController.phpapp/Http/Middleware/RoomAuthenticate.phpapp/Services/RoomFileService.phpresources/js/components/RoomJoinButton.vueresources/js/components/RoomTabFiles.vueresources/js/components/RoomTabFilesViewButton.vueresources/js/components/RoomTabRecordings.vueresources/js/components/RoomTabRecordingsViewButton.vueresources/js/constants/httpCustomErrorMessages.jsresources/js/views/RoomsView.vueresources/views/errors/404.blade.phproutes/api.phproutes/web.phptests/Backend/Feature/api/v1/RecordingTest.phptests/Backend/Feature/api/v1/Room/FileTest.phptests/Backend/Feature/api/v1/Room/RoomTest.phptests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js
💤 Files with no reviewable changes (3)
- app/Http/Controllers/api/v1/RecordingFormatController.php
- routes/api.php
- resources/js/components/RoomTabFilesViewButton.vue
…ess-not-working-in-restrictive-browsers
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/Backend/Feature/api/v1/RecordingTest.php (1)
679-682: Minor: Unusual method chain formatting.The method chain break between lines 681-682 is syntactically valid but unconventional. Consider reformatting for consistency with the rest of the file.
Suggested formatting
// Access as guest with valid room auth token - $this->withCookies([ - session()->getName() => $currentSession->id, - ])-> - get(route('rooms.recordings.formats.show', [ + $this->withCookies([ + session()->getName() => $currentSession->id, + ])->get(route('rooms.recordings.formats.show', [ 'room' => $recording->room->id,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Backend/Feature/api/v1/RecordingTest.php` around lines 679 - 682, The test's method chaining for withCookies() -> get() is split across lines in an unconventional way; locate the call to withCookies(...) followed by ->get(route('rooms.recordings.formats.show', ...)) and reformat the chain so the arrow and method are consistently placed (either keep the -> at the end of the withCookies(...) line or put get(...) on the same line) to match the file's existing chaining style (adjust the call wrapping of withCookies() and the subsequent get() invocation accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/Http/Controllers/RecordingController.php`:
- Around line 52-53: Replace the insecure substring check with a proper
realpath-based containment check: resolve realpath() for both the requested
$absFilePath and the allowed directory
(Storage::disk('recordings')->path($allowedDir)), verify neither realpath call
returned false (reject if they did), normalize directory path with a trailing
DIRECTORY_SEPARATOR, and then ensure the file path begins with the canonical
allowed-dir path (e.g. check that strpos($fileRealPath, $allowedDirReal) === 0
or use Str::startsWith on the resolved values) to prevent prefix-similar
bypasses; update the condition around $absFilePath/$allowedDir in
RecordingController to implement this.
In `@docker/app/nginx/snippets/security-header.conf`:
- Around line 17-19: The global Cross-Origin-Opener-Policy header
("same-origin") breaks popup->opener signaling used by new-tab-error.blade.php
and 404.blade.php; update the add_header directive that sets
Cross-Origin-Opener-Policy to use "same-origin-allow-popups" instead of
"same-origin" so window.opener.postMessage() continues to work for popups while
maintaining COOP protections.
---
Nitpick comments:
In `@tests/Backend/Feature/api/v1/RecordingTest.php`:
- Around line 679-682: The test's method chaining for withCookies() -> get() is
split across lines in an unconventional way; locate the call to withCookies(...)
followed by ->get(route('rooms.recordings.formats.show', ...)) and reformat the
chain so the arrow and method are consistently placed (either keep the -> at the
end of the withCookies(...) line or put get(...) on the same line) to match the
file's existing chaining style (adjust the call wrapping of withCookies() and
the subsequent get() invocation accordingly).
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/Http/Controllers/RecordingController.phpdocker/app/nginx/snippets/security-header.conftests/Backend/Feature/api/v1/RecordingTest.php
| if (! str_contains($absFilePath, realpath(Storage::disk('recordings')->path($allowedDir)))) { | ||
| // prevent path transversal |
There was a problem hiding this comment.
Fix path traversal check: str_contains is bypassable.
Line 52 uses substring matching for directory containment. That can be bypassed by prefix-similar paths (e.g., .../presentation2/...) and is unsafe if realpath($allowedDir) resolves to false. This is a file access boundary break.
🔒 Proposed fix
- // Check if resolved requested file path is in allowed directory
- if (! str_contains($absFilePath, realpath(Storage::disk('recordings')->path($allowedDir)))) {
+ $allowedAbsDir = realpath(Storage::disk('recordings')->path($allowedDir));
+
+ // Check if resolved requested file path is in allowed directory
+ if (
+ $allowedAbsDir === false ||
+ ! str_starts_with(
+ $absFilePath,
+ rtrim($allowedAbsDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR
+ )
+ ) {
// prevent path transversal
Log::notice('Attempted to access recording file outside of allowed directory', ['requestedFile' => $requestedFile]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (! str_contains($absFilePath, realpath(Storage::disk('recordings')->path($allowedDir)))) { | |
| // prevent path transversal | |
| $allowedAbsDir = realpath(Storage::disk('recordings')->path($allowedDir)); | |
| // Check if resolved requested file path is in allowed directory | |
| if ( | |
| $allowedAbsDir === false || | |
| ! str_starts_with( | |
| $absFilePath, | |
| rtrim($allowedAbsDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR | |
| ) | |
| ) { | |
| // prevent path transversal |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/Http/Controllers/RecordingController.php` around lines 52 - 53, Replace
the insecure substring check with a proper realpath-based containment check:
resolve realpath() for both the requested $absFilePath and the allowed directory
(Storage::disk('recordings')->path($allowedDir)), verify neither realpath call
returned false (reject if they did), normalize directory path with a trailing
DIRECTORY_SEPARATOR, and then ensure the file path begins with the canonical
allowed-dir path (e.g. check that strpos($fileRealPath, $allowedDirReal) === 0
or use Str::startsWith on the resolved values) to prevent prefix-similar
bypasses; update the condition around $absFilePath/$allowedDir in
RecordingController to implement this.
…ess-not-working-in-restrictive-browsers
…ess-not-working-in-restrictive-browsers
There was a problem hiding this comment.
🧹 Nitpick comments (1)
CHANGELOG.md (1)
25-25: Clarify the failure trigger in changelog wordingAt Line 25, consider explicitly mentioning the async access-link flow as the trigger (not just generic new-window restrictions), so the fix context is more precise for readers.
Suggested wording
-- Recording access blocked in some browsers due to restrictions on opening new windows without user interaction ([`#2851`], [`#2901`]) +- Recording access could fail in some browsers when the access link was generated asynchronously before opening a new tab, triggering user-interaction restrictions ([`#2851`], [`#2901`])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` at line 25, Update the changelog entry that currently reads "Recording access blocked in some browsers due to restrictions on opening new windows without user interaction ([`#2851`], [`#2901`])" to explicitly state that the failure was triggered by the async access-link flow (not just generic new-window restrictions); edit the sentence to mention "async access-link flow" as the trigger and keep the issue references ([`#2851`], [`#2901`]) intact so readers understand the precise context of the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@CHANGELOG.md`:
- Line 25: Update the changelog entry that currently reads "Recording access
blocked in some browsers due to restrictions on opening new windows without user
interaction ([`#2851`], [`#2901`])" to explicitly state that the failure was
triggered by the async access-link flow (not just generic new-window
restrictions); edit the sentence to mention "async access-link flow" as the
trigger and keep the issue references ([`#2851`], [`#2901`]) intact so readers
understand the precise context of the fix.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js (2)
28-71: Extract repeated link/message assertions into test helpersThe same assertion and
postMessagelogic is duplicated across multiple scenarios. This makes future route or message contract changes expensive and error-prone.♻️ Proposed refactor
+const assertRecordingFormatLink = ({ button, formatTypeKey, formatId, auth }) => { + cy.get(`[data-test="${button}"]`) + .should("be.visible") + .and("include.text", formatTypeKey) + .then(($a) => { + const url = new URL($a.attr("href")); + expect(url.pathname).to.eq( + `/rooms/abc-def-123/recordings/e0cfa18c5fd75a42bd7947d8549321b03abf1daf-1660728035/formats/${formatId}`, + ); + if (auth) { + expect(url.searchParams.get("room_auth_token")).to.eq(auth.token); + expect(url.searchParams.get("room_auth_token_type")).to.eq(String(auth.type)); + } + }) + .and("have.attr", "rel", "opener") + .and("have.attr", "target", "_blank"); +}; + +const postRecordingMessage = (type) => { + cy.window().then(($window) => { + $window.postMessage(type ? { type } : null, Cypress.config("baseUrl")); + }); +};Also applies to: 136-179, 360-403, 247-252, 291-296, 457-462, 488-493, 522-527, 563-568, 628-633, 654-656, 666-670, 679-685, 716-721
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js` around lines 28 - 71, The repeated assertions for recording format links (examples: the cy.get('[data-test="notes-button"]') / podcast-button / presentation-button / screenshare-button blocks) should be extracted into a reusable Cypress helper or custom command (e.g., assertRecordingFormatButton or Cypress.Commands.add('assertRecordingFormatButton')). Implement the helper to accept the data-test selector (or button name), the expected i18n text key (e.g., "rooms.recordings.format_types.notes") and the format id (1–4) and then run the chain of .should checks including href composition with Cypress.config("baseUrl"), rel and target assertions; replace each duplicated block with calls to that helper across all listed occurrences (lines referencing notes-button, podcast-button, presentation-button, screenshare-button and the other similar selectors).
31-36: Prefer semantic URL assertions over full-string equalityCurrent href checks are very strict on serialization details. Asserting
pathname+ required query params is less flaky while still validating behavior.Also applies to: 42-47, 53-58, 65-69, 140-144, 151-155, 162-166, 173-177, 364-368, 375-379, 386-390, 397-401
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js` around lines 31 - 36, Replace the brittle full-string href equality checks in the RoomsView recordings tests with semantic URL assertions: for each occurrence of the chain that calls .and("have.attr","href", Cypress.config("baseUrl") + "...") (e.g., in RoomsViewRecordingsRecordingActions.cy.js where the href is built via Cypress.config("baseUrl") + "/rooms/abc-def-123/recordings/.../formats/1"), parse the anchor's href using the URL API inside a .should callback and assert url.pathname equals the expected path ("/rooms/abc-def-123/recordings/.../formats/1") and that required query params exist/have expected values via url.searchParams.has/.get; apply this change to all listed occurrences (lines around the given diffs and the other ranges in the comment) so tests validate pathname and necessary query params instead of exact serialized baseUrl + href string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@resources/js/components/RoomTabRecordingsViewButton.vue`:
- Around line 114-132: The viewFormatUrl function currently concatenates strings
using settingsStore.getSetting("general.base_url") and router.resolve(...).href
and appends raw room_auth_token fields, which can produce malformed URLs and
unescaped params; change viewFormatUrl to use the URL constructor with a safe
fallback base (e.g. "http://localhost/") when general.base_url is falsy, build
the path using router.resolve(...).href appended to that URL object, and add
token values via URL.searchParams.set to ensure proper encoding (use
props.roomAuthToken.id and props.roomAuthToken.type only when present). Ensure
you update only the viewFormatUrl helper so it returns the properly encoded
href.
---
Nitpick comments:
In `@tests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js`:
- Around line 28-71: The repeated assertions for recording format links
(examples: the cy.get('[data-test="notes-button"]') / podcast-button /
presentation-button / screenshare-button blocks) should be extracted into a
reusable Cypress helper or custom command (e.g., assertRecordingFormatButton or
Cypress.Commands.add('assertRecordingFormatButton')). Implement the helper to
accept the data-test selector (or button name), the expected i18n text key
(e.g., "rooms.recordings.format_types.notes") and the format id (1–4) and then
run the chain of .should checks including href composition with
Cypress.config("baseUrl"), rel and target assertions; replace each duplicated
block with calls to that helper across all listed occurrences (lines referencing
notes-button, podcast-button, presentation-button, screenshare-button and the
other similar selectors).
- Around line 31-36: Replace the brittle full-string href equality checks in the
RoomsView recordings tests with semantic URL assertions: for each occurrence of
the chain that calls .and("have.attr","href", Cypress.config("baseUrl") + "...")
(e.g., in RoomsViewRecordingsRecordingActions.cy.js where the href is built via
Cypress.config("baseUrl") + "/rooms/abc-def-123/recordings/.../formats/1"),
parse the anchor's href using the URL API inside a .should callback and assert
url.pathname equals the expected path
("/rooms/abc-def-123/recordings/.../formats/1") and that required query params
exist/have expected values via url.searchParams.has/.get; apply this change to
all listed occurrences (lines around the given diffs and the other ranges in the
comment) so tests validate pathname and necessary query params instead of exact
serialized baseUrl + href string.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 018b9660-2eb8-4912-bab4-1b807fe3219a
📒 Files selected for processing (2)
resources/js/components/RoomTabRecordingsViewButton.vuetests/Frontend/e2e/RoomsViewRecordingsRecordingActions.cy.js
Fixes #2851
Type
Checklist
Changes
Other information
Summary by CodeRabbit
New Features
Bug Fixes
Security