Skip to content

Implement new cozy-external-bridge setup#2889

Open
zatteo wants to merge 2 commits intomainfrom
new-setup-bridge
Open

Implement new cozy-external-bridge setup#2889
zatteo wants to merge 2 commits intomainfrom
new-setup-bridge

Conversation

@zatteo
Copy link
Member

@zatteo zatteo commented Feb 23, 2026

Applying changes from here linagora/linagora-design-flutter#69

I will need to update pubspec.

Summary by CodeRabbit

  • Configuration Updates

    • Added a configurable allowlist for the external bridge (defaults: .twake.app, .lin-saas.com, .lin-saas.dev)
    • Updated external bridge default version to 1.2.1
    • Environment and JSON loading now respect the allowlist and log its resolved value
  • Updates

    • Simplified container detection paths for app bar, notifications, and permission requests to use a synchronous in-container check
  • Chores

    • Adjusted a design dependency reference to a feature branch

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Walkthrough

The PR adds a public cozyExternalBridgeAllowlist (List) to AppConfig with defaults and environment/JSON loading, changes cozyExternalBridgeVersion default to 1.2.1, and logs the resolved environment value. The web startup now passes validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist to CozyConfigManager.initialize. Several modules replace awaited isInsideCozy checks with synchronous isInContainer checks. The linagora_design_flutter git dependency ref was updated.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and incomplete. It lacks required sections such as Root cause, Solution, Impact description, Test recommendations, Pre-merge checklist, and Resolved section from the template. Provide a complete description following the template with details on the solution, impact, testing approach, and any pre-merge requirements.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement new cozy-external-bridge setup' directly matches the main objective of introducing a new configuration system for the Cozy External Bridge, including the allowlist feature and version update.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-setup-bridge

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

if (json['cozy_external_bridge_allowlist'] is List<String> &&
json['cozy_external_bridge_allowlist'].isNotEmpty) {
cozyExternalBridgeAllowlist = json['cozy_external_bridge_allowlist'];
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to be able to add new approved domain name for bridge in env var or in config.json. It will help us:

  • ease development experience by adding for example in config.json 'localhost:8080'
  • allow to deploy tchat in a container for any domain name

However, for config.json, it seems that it is loaded after CozyBridge initialization. Any thought on this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a method to initialize config early, put only the cozy config initialization there, then call as soon as the app start.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
lib/widgets/matrix.dart (1)

529-540: Silent catch swallows the exception without logging.

If CozyConfigManager().isInContainer or requestNotificationPermission() throws, the error is silently discarded and the fallback path runs with no diagnostic information.

♻️ Proposed fix
     } catch (e) {
+      Logs().w('_requestNotificationPermission: falling back to browser permission', e);
       html.Notification.requestPermission();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/matrix.dart` around lines 529 - 540, In
_requestNotificationPermission, the current catch block swallows exceptions from
CozyConfigManager().isInContainer or
CozyConfigManager().requestNotificationPermission(); update the catch to capture
the exception and stack trace and log them (e.g., via print/debugPrint or the
app logger) before falling back to html.Notification.requestPermission(); ensure
you reference CozyConfigManager(), its requestNotificationPermission method, and
html.Notification.requestPermission so the error context is clear in the log.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/config/app_config.dart`:
- Around line 223-225: cozyExternalBridgeAllowlist is built from
_cozyExternalBridgeAllowlistEnv.split(',') which yields [''] when the env var is
explicitly empty; change the construction so that when
_cozyExternalBridgeAllowlistEnv is empty or only whitespace you pass an empty
list to CozyConfigManager().initialize(validUrlSuffixes: ...), otherwise split
on ',' and trim/filter out any empty strings (e.g., map trim and where not
empty) before calling CozyConfigManager().initialize to avoid passing a single
empty-string entry.
- Around line 317-320: The runtime type check using `is List<String>` on
`json['cozy_external_bridge_allowlist']` always fails because JSON arrays decode
to `List<dynamic>`; update the code around `cozyExternalBridgeAllowlist` so it
checks `is List` (or `is List<dynamic>`), then convert/validate the elements to
strings (e.g., map/cast each element to `String` and filter non-strings) and
assign the resulting `List<String>` only if non-empty; reference
`cozyExternalBridgeAllowlist` and `json['cozy_external_bridge_allowlist']` when
applying this change.

In `@lib/main.dart`:
- Around line 68-71: The app is starting the UI before CozyConfigManager
finishes setup, causing isInContainer reads (used by AdaptiveScaffoldAppBar) to
be stale; change the startup sequence so injectCozyScript(...) completes and
CozyConfigManager().initialize(...) is awaited before calling startGui(clients).
Locate the chain that calls CozyConfigManager().injectCozyScript and
CozyConfigManager().initialize and make startGui(clients) run only after the
Future returned by injectCozyScript().then(...) resolves (or use async/await),
ensuring isInContainer is set before the first widget build.

In `@lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart`:
- Around line 53-57: The code races because CozyConfigManager().initialize(...)
(called via .then()) isn’t awaited before startGui() runs, so
adaptive_scaffold_appbar's check CozyConfigManager().isInContainer can read an
uninitialized value; fix by awaiting the initialization sequence on the
PlatformInfos.isWeb branch — await
CozyConfigManager().injectCozyScript(AppConfig.cozyExternalBridgeVersion) and
then await CozyConfigManager().initialize(validUrlSuffixes:
AppConfig.cozyExternalBridgeAllowlist) (instead of chaining with .then()), and
only call startGui() after those awaits complete.

---

Nitpick comments:
In `@lib/widgets/matrix.dart`:
- Around line 529-540: In _requestNotificationPermission, the current catch
block swallows exceptions from CozyConfigManager().isInContainer or
CozyConfigManager().requestNotificationPermission(); update the catch to capture
the exception and stack trace and log them (e.g., via print/debugPrint or the
app logger) before falling back to html.Notification.requestPermission(); ensure
you reference CozyConfigManager(), its requestNotificationPermission method, and
html.Notification.requestPermission so the error context is clear in the log.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61c3f06 and e66978e.

📒 Files selected for processing (5)
  • lib/config/app_config.dart
  • lib/main.dart
  • lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart
  • lib/widgets/local_notifications_extension.dart
  • lib/widgets/matrix.dart

Comment on lines 68 to 71
CozyConfigManager()
.injectCozyScript(AppConfig.cozyExternalBridgeVersion)
.then((value) => CozyConfigManager().initialize());
.then((value) => CozyConfigManager().initialize(validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CozyConfigManager().initialize() is fire-and-forget — race condition with initial widget builds.

injectCozyScript(...).then(initialize) is not awaited. startGui(clients) executes immediately after, so isInContainer can be false during the first widget tree render (including AdaptiveScaffoldAppBar). Unlike the previous FutureBuilder(isInsideCozy) approach, which automatically rebuilt when the future resolved, the new synchronous isInContainer reads have no rebuild trigger, meaning the app bar may remain visible inside a Cozy container for the entire session if no parent rebuild occurs.

🐛 Proposed fix — await the initialization chain before rendering
   if (PlatformInfos.isWeb) {
-    CozyConfigManager()
-        .injectCozyScript(AppConfig.cozyExternalBridgeVersion)
-        .then((value) => CozyConfigManager().initialize(validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist));
+    await CozyConfigManager()
+        .injectCozyScript(AppConfig.cozyExternalBridgeVersion)
+        .then((_) => CozyConfigManager().initialize(
+              validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist,
+            ));
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/main.dart` around lines 68 - 71, The app is starting the UI before
CozyConfigManager finishes setup, causing isInContainer reads (used by
AdaptiveScaffoldAppBar) to be stale; change the startup sequence so
injectCozyScript(...) completes and CozyConfigManager().initialize(...) is
awaited before calling startGui(clients). Locate the chain that calls
CozyConfigManager().injectCozyScript and CozyConfigManager().initialize and make
startGui(clients) run only after the Future returned by
injectCozyScript().then(...) resolves (or use async/await), ensuring
isInContainer is set before the first widget build.

Comment on lines +53 to +57
if(CozyConfigManager().isInContainer) {
return const SizedBox();
}

return const SizedBox();
},
);
return child;
Copy link

@coderabbitai coderabbitai bot Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for CozyConfigManager class definition
fd "cozy_config_manager" --type f

Repository: linagora/twake-on-matrix

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Search for CozyConfigManager class definition and factory constructor
rg -n "class CozyConfigManager" --type dart -A 10

Repository: linagora/twake-on-matrix

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Search for isInContainer property and initialize method
rg -n "isInContainer" --type dart -B 2 -A 2

Repository: linagora/twake-on-matrix

Length of output: 1478


🏁 Script executed:

#!/bin/bash
# Search for initialize method in CozyConfigManager context
rg -n "initialize\(\)" --type dart -B 2 -A 5 | head -100

Repository: linagora/twake-on-matrix

Length of output: 4901


🏁 Script executed:

#!/bin/bash
# Find where CozyConfigManager is imported from
rg -n "import.*[Cc]ozy|from.*cozy" --type dart | head -20

Repository: linagora/twake-on-matrix

Length of output: 422


🏁 Script executed:

#!/bin/bash
# Search for CozyConfigManager.initialize() calls
rg -n "CozyConfigManager\(\)\.initialize" --type dart -B 2 -A 2

Repository: linagora/twake-on-matrix

Length of output: 359


🏁 Script executed:

#!/bin/bash
# Search main.dart for initialize setup
fd "main.dart" --type f

Repository: linagora/twake-on-matrix

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Read main.dart to understand the initialization sequence and race condition
cat -n lib/main.dart | sed -n '50,100p'

Repository: linagora/twake-on-matrix

Length of output: 2355


Race condition in main.dart: CozyConfigManager().initialize() is not awaited before GUI rendering.

The initialization chain on lines 68–70 of main.dart is fire-and-forget (chained with .then()), while startGui() on line 77 is called immediately without waiting. This means the widget at line 53–57 will read CozyConfigManager().isInContainer during the first build, but the initialize() call likely hasn't completed yet, causing the check to read an uninitialized value.

Fix: Add await to the initialization chain in main.dart:

if (PlatformInfos.isWeb) {
  await CozyConfigManager()
      .injectCozyScript(AppConfig.cozyExternalBridgeVersion)
      .then((value) => CozyConfigManager().initialize(validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart` around
lines 53 - 57, The code races because CozyConfigManager().initialize(...)
(called via .then()) isn’t awaited before startGui() runs, so
adaptive_scaffold_appbar's check CozyConfigManager().isInContainer can read an
uninitialized value; fix by awaiting the initialization sequence on the
PlatformInfos.isWeb branch — await
CozyConfigManager().injectCozyScript(AppConfig.cozyExternalBridgeVersion) and
then await CozyConfigManager().initialize(validUrlSuffixes:
AppConfig.cozyExternalBridgeAllowlist) (instead of chaining with .then()), and
only call startGui() after those awaits complete.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangdat I agree it is an issue but I suppose it was built on purpose to not await it right? Any thoughts?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 102 to +199
@@ -193,6 +194,11 @@ abstract class AppConfig {
defaultValue: ConfigurationSaas.homeserver,
);

static const String _cozyExternalBridgeAllowlistEnv = String.fromEnvironment(
'COZY_EXTERNAL_BRIDGE_ALLOWLIST',
defaultValue: '.twake.app,.lin-saas.com,.lin-saas.dev',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A const should be defined for these default values, for reuse.

Copy link
Collaborator

@tddang-linagora tddang-linagora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Please include the pubspec.yaml and pubspec.lock in this PR, which contain the changes made in linagora-design-flutter (else CozyConfigManager().isInContainer won't compile).

if (json['cozy_external_bridge_allowlist'] is List<String> &&
json['cozy_external_bridge_allowlist'].isNotEmpty) {
cozyExternalBridgeAllowlist = json['cozy_external_bridge_allowlist'];
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a method to initialize config early, put only the cozy config initialization there, then call as soon as the app start.

@zatteo
Copy link
Member Author

zatteo commented Feb 26, 2026

Create a method to initialize config early, put only the cozy config initialization there, then call as soon as the app start.

What do you mean? Can you detail?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/main.dart (1)

68-71: ⚠️ Potential issue | 🟠 Major

Await Cozy initialization before GUI startup to prevent stale container state.

At Line 68–71, the Future chain is fire-and-forget; startGui(clients) at Line 77 can run before initialize(...) completes, so synchronous isInContainer reads may be wrong on initial render.

Proposed fix
   if (PlatformInfos.isWeb) {
-    CozyConfigManager()
-        .injectCozyScript(AppConfig.cozyExternalBridgeVersion)
-        .then((value) => CozyConfigManager().initialize(validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist));
+    await CozyConfigManager().injectCozyScript(
+      AppConfig.cozyExternalBridgeVersion,
+    );
+    await CozyConfigManager().initialize(
+      validUrlSuffixes: AppConfig.cozyExternalBridgeAllowlist,
+    );
   }
#!/bin/bash
# Verify startup ordering and container-state read sites
sed -n '60,85p' lib/main.dart
rg -nP --type=dart '\bisInContainer\b' lib -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/main.dart` around lines 68 - 71, The GUI can start before
CozyConfigManager finishes, causing stale isInContainer reads; change the
fire-and-forget chain so that injectCozyScript(...).then(...initialize...) is
awaited before calling startGui(clients). Specifically, await
CozyConfigManager().injectCozyScript(AppConfig.cozyExternalBridgeVersion) and
then await CozyConfigManager().initialize(validUrlSuffixes:
AppConfig.cozyExternalBridgeAllowlist) (or await the combined Future) and only
after those awaits call startGui(clients); also scan sites using isInContainer
to ensure they read state after this initialization completes.
🧹 Nitpick comments (1)
pubspec.yaml (1)

23-23: Consider pinning linagora_design_flutter to commit SHA for explicit release stability.

Using a branch ref (feat/update-cozy-external-bridge-setup) at Line 23 and Line 335 relies on pubspec.lock to capture the resolved commit SHA (4331015b1f10c4b2033493e756ebf86398185d8d). While this is standard practice in Dart/Flutter projects, explicitly pinning to the commit SHA in pubspec.yaml makes release intent clearer and removes the need to verify lock file consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pubspec.yaml` at line 23, Update the linagora_design_flutter dependency entry
in pubspec.yaml to pin to the specific commit SHA instead of the branch name:
locate the linagora_design_flutter dependency block (the line containing "ref:
feat/update-cozy-external-bridge-setup") and replace the branch ref with the
resolved commit SHA (e.g., "ref: 4331015b1f10c4b2033493e756ebf86398185d8d") so
the dependency is explicitly fixed to that commit for release stability.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lib/main.dart`:
- Around line 68-71: The GUI can start before CozyConfigManager finishes,
causing stale isInContainer reads; change the fire-and-forget chain so that
injectCozyScript(...).then(...initialize...) is awaited before calling
startGui(clients). Specifically, await
CozyConfigManager().injectCozyScript(AppConfig.cozyExternalBridgeVersion) and
then await CozyConfigManager().initialize(validUrlSuffixes:
AppConfig.cozyExternalBridgeAllowlist) (or await the combined Future) and only
after those awaits call startGui(clients); also scan sites using isInContainer
to ensure they read state after this initialization completes.

---

Nitpick comments:
In `@pubspec.yaml`:
- Line 23: Update the linagora_design_flutter dependency entry in pubspec.yaml
to pin to the specific commit SHA instead of the branch name: locate the
linagora_design_flutter dependency block (the line containing "ref:
feat/update-cozy-external-bridge-setup") and replace the branch ref with the
resolved commit SHA (e.g., "ref: 4331015b1f10c4b2033493e756ebf86398185d8d") so
the dependency is explicitly fixed to that commit for release stability.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e66978e and 9adfcb3.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • lib/config/app_config.dart
  • lib/main.dart
  • lib/widgets/layouts/adaptive_layout/adaptive_scaffold_appbar.dart
  • lib/widgets/local_notifications_extension.dart
  • lib/widgets/matrix.dart
  • pubspec.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/config/app_config.dart
  • lib/widgets/matrix.dart

@github-actions
Copy link
Contributor

This PR has been deployed to https://linagora.github.io/twake-on-matrix/2889

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants