Add Chatwoot plugin and provider#126
Conversation
Add Chatwoot plugin and provider
📝 WalkthroughWalkthroughThis PR introduces a complete Chatwoot live chat widget plugin for Filament, including documentation, configuration files, plugin manifest, main plugin class with settings form, and a service provider that injects the Chatwoot SDK into panel rendering. Changes
Sequence DiagramsequenceDiagram
participant Admin as Admin User
participant Panel as Filament Panel
participant Plugin as ChatwootPlugin
participant Config as Configuration
participant Provider as ChatwootPluginProvider
participant Browser as Browser/Render Hook
Admin->>Panel: Access plugin settings
Panel->>Plugin: Load settings form
Plugin->>Config: Read base_url & website_token
Config-->>Plugin: Return config values
Plugin-->>Admin: Display prefilled form
Admin->>Plugin: Submit settings
Plugin->>Config: Write CHATWOOT_BASE_URL & CHATWOOT_WEBSITE_TOKEN env vars
Config-->>Plugin: Settings persisted
Plugin-->>Admin: Show success notification
Panel->>Provider: Boot service provider
Provider->>Config: Retrieve base_url & website_token
Config-->>Provider: Return values
alt Config values present
Provider->>Browser: Register render hook
Browser->>Browser: Inject Chatwoot SDK script
Browser->>Browser: Initialize window.chatwootSDK.run()
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
chatwoot/README.md (1)
1-13: Consider expanding the README with installation & configuration instructions.The README only lists features and credits. Users benefit from a short section documenting:
- The required env vars (
CHATWOOT_BASE_URL,CHATWOOT_WEBSITE_TOKEN) or where to configure them in the settings UI.- Where to obtain the
website_tokenfrom Chatwoot.- Expected
base_urlformat (with or without trailing slash — see related comment on the provider).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@chatwoot/README.md` around lines 1 - 13, Update the README to add an Installation & Configuration section that documents required environment variables CHATWOOT_BASE_URL and CHATWOOT_WEBSITE_TOKEN (and/or where to set them in the plugin settings UI), explain how to obtain the website_token from the Chatwoot dashboard, and clarify the expected base_url format (include example with and without trailing slash and state which is accepted); reference these exact symbols (CHATWOOT_BASE_URL, CHATWOOT_WEBSITE_TOKEN, website_token, Chatwoot settings/UI) so users can quickly map README instructions to the code/config.chatwoot/src/ChatwootPlugin.php (1)
25-28: Add URL validation (and consider normalizing) thebase_urlinput.
base_urlis used as a URL prefix in the injected script (BASE_URL + "/packs/js/sdk.js"). Without validation, a typo or trailing slash will silently break SDK loading. Consider->url()(and optionally stripping a trailing slash insaveSettings).♻️ Proposed fix
TextInput::make('base_url') ->label('Base URL') ->required() + ->url() ->default(fn () => config('chatwoot.base_url')),And in
saveSettings:$this->writeToEnvironment([ - 'CHATWOOT_BASE_URL' => $data['base_url'], + 'CHATWOOT_BASE_URL' => rtrim($data['base_url'], '/'), 'CHATWOOT_WEBSITE_TOKEN' => $data['website_token'], ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@chatwoot/src/ChatwootPlugin.php` around lines 25 - 28, The base_url TextInput currently lacks URL validation and normalization; update TextInput::make('base_url') to include URL validation (e.g., ->url()) and client-side requirement, and in the plugin's saveSettings method normalize the value (strip any trailing slash and optionally ensure a valid scheme like https://) before persisting so BASE_URL used to build BASE_URL + "/packs/js/sdk.js" will always be a well-formed prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@chatwoot/src/ChatwootPlugin.php`:
- Around line 12-46: The class and method opening braces must be moved to their
own lines to satisfy Pint rules: update the ChatwootPlugin class declaration and
every method signature (getId, register, boot, getSettingsForm, saveSettings) so
the "{" is on the next line (e.g., change "class ChatwootPlugin implements
HasPluginSettings, Plugin {" to have the "{" on a new line, and do the same for
each function declaration). Ensure all method-level braces and the class-level
brace follow this style consistently to resolve class_definition,
braces_position, and single_class_elements lint errors.
In `@chatwoot/src/Providers/ChatwootPluginProvider.php`:
- Around line 10-11: The class and method opening braces are on the same line
which violates PSR-12/Pint; update the class ChatwootPluginProvider and its
method boot() so each opening brace is placed on the following line (i.e., put
the "{" for class ChatwootPluginProvider on its own line and likewise put the
"{" for public function boot() on the next line) to satisfy the class_definition
and brace lint rules.
- Around line 18-40: The injected script currently interpolates Blade-escaped
strings directly into JS string literals causing possible JS injection; update
the Blade::render call to serialize the values with
Illuminate\Support\Js::from() (or the `@js` directive) so baseUrl and websiteToken
are safely JS-encoded before being used in the script where
window.chatwootSDK.run is called; also normalize baseUrl to remove a trailing
slash (so the computed g.src = baseUrl + "/packs/js/sdk.js" never produces a
double slash) and reference the serialized values (Js::from($baseUrl) and
Js::from($websiteToken)) instead of raw {{ $baseUrl }} / {{ $websiteToken }}
inside the Blade::render template.
---
Nitpick comments:
In `@chatwoot/README.md`:
- Around line 1-13: Update the README to add an Installation & Configuration
section that documents required environment variables CHATWOOT_BASE_URL and
CHATWOOT_WEBSITE_TOKEN (and/or where to set them in the plugin settings UI),
explain how to obtain the website_token from the Chatwoot dashboard, and clarify
the expected base_url format (include example with and without trailing slash
and state which is accepted); reference these exact symbols (CHATWOOT_BASE_URL,
CHATWOOT_WEBSITE_TOKEN, website_token, Chatwoot settings/UI) so users can
quickly map README instructions to the code/config.
In `@chatwoot/src/ChatwootPlugin.php`:
- Around line 25-28: The base_url TextInput currently lacks URL validation and
normalization; update TextInput::make('base_url') to include URL validation
(e.g., ->url()) and client-side requirement, and in the plugin's saveSettings
method normalize the value (strip any trailing slash and optionally ensure a
valid scheme like https://) before persisting so BASE_URL used to build BASE_URL
+ "/packs/js/sdk.js" will always be a well-formed prefix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6912c5a5-2051-4f00-9c99-9369e0497aa4
📒 Files selected for processing (5)
chatwoot/README.mdchatwoot/config/chatwoot.phpchatwoot/plugin.jsonchatwoot/src/ChatwootPlugin.phpchatwoot/src/Providers/ChatwootPluginProvider.php
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: Lint
chatwoot/src/Providers/ChatwootPluginProvider.php
[error] 1-1: Laravel Pint reported style issues: class_definition, brace.
chatwoot/src/ChatwootPlugin.php
[error] 1-1: Laravel Pint reported style issues: class_definition, braces_position, single_class_elements.
🪛 PHPMD (2.15.0)
chatwoot/src/ChatwootPlugin.php
[warning] 19-19: Avoid unused parameters such as '$panel'. (undefined)
(UnusedFormalParameter)
[warning] 21-21: Avoid unused parameters such as '$panel'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (3)
chatwoot/config/chatwoot.php (1)
1-6: LGTM!Config file cleanly exposes the two env-backed values consumed by the provider and plugin class.
chatwoot/plugin.json (1)
1-15: Manifest looks fine.Identity/metadata fields mirror the
ChatwootPluginclass and match sibling plugins' conventions. See the separate comment onChatwootPluginProvider.phpregarding how the service provider gets loaded — confirm the host loader reads PHP service providers implicitly, otherwise the render hook will never register.chatwoot/src/Providers/ChatwootPluginProvider.php (1)
1-44: Verify whether the plugin loader auto-discovers service providers.The
ChatwootPluginProvideris not explicitly registered inChatwootPlugin.boot()or referenced inplugin.json. However, this follows the identical pattern used byTawktoWidgetPluginand other plugins in the repository (e.g.,SubdomainsPluginProvider,UserCreatableServersPluginProvider,TicketsPluginProvider), which suggests a convention-based auto-discovery mechanism may exist in the main application's plugin loader.To confirm the widget will render, verify that the main Pelican application auto-discovers service providers from the
{PluginNamespace}\Providers\{PluginId}PluginProviderconvention. If no such auto-discovery exists, the render hook will not fire and the provider must be explicitly registered.
| class ChatwootPlugin implements HasPluginSettings, Plugin { | ||
| use EnvironmentWriterTrait; | ||
|
|
||
| public function getId(): string { | ||
| return 'chatwoot'; | ||
| } | ||
|
|
||
| public function register(Panel $panel): void {} | ||
|
|
||
| public function boot(Panel $panel): void {} | ||
|
|
||
| public function getSettingsForm(): array { | ||
| return [ | ||
| TextInput::make('base_url') | ||
| ->label('Base URL') | ||
| ->required() | ||
| ->default(fn () => config('chatwoot.base_url')), | ||
| TextInput::make('website_token') | ||
| ->label('Website Token') | ||
| ->required() | ||
| ->default(fn () => config('chatwoot.website_token')), | ||
| ]; | ||
| } | ||
|
|
||
| public function saveSettings(array $data): void { | ||
| $this->writeToEnvironment([ | ||
| 'CHATWOOT_BASE_URL' => $data['base_url'], | ||
| 'CHATWOOT_WEBSITE_TOKEN' => $data['website_token'], | ||
| ]); | ||
|
|
||
| Notification::make() | ||
| ->title('Settings saved') | ||
| ->success() | ||
| ->send(); | ||
| } |
There was a problem hiding this comment.
Fix Pint style: opening braces on their own line for class and all methods.
The Lint pipeline is failing (class_definition, braces_position, single_class_elements). Move every { on the class/method signature lines to the next line.
🎨 Proposed fix
-class ChatwootPlugin implements HasPluginSettings, Plugin {
+class ChatwootPlugin implements HasPluginSettings, Plugin
+{
use EnvironmentWriterTrait;
- public function getId(): string {
+ public function getId(): string
+ {
return 'chatwoot';
}
public function register(Panel $panel): void {}
public function boot(Panel $panel): void {}
- public function getSettingsForm(): array {
+ public function getSettingsForm(): array
+ {
return [
...
];
}
- public function saveSettings(array $data): void {
+ public function saveSettings(array $data): void
+ {
$this->writeToEnvironment([
...
]);
...
}
}🧰 Tools
🪛 PHPMD (2.15.0)
[warning] 19-19: Avoid unused parameters such as '$panel'. (undefined)
(UnusedFormalParameter)
[warning] 21-21: Avoid unused parameters such as '$panel'. (undefined)
(UnusedFormalParameter)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@chatwoot/src/ChatwootPlugin.php` around lines 12 - 46, The class and method
opening braces must be moved to their own lines to satisfy Pint rules: update
the ChatwootPlugin class declaration and every method signature (getId,
register, boot, getSettingsForm, saveSettings) so the "{" is on the next line
(e.g., change "class ChatwootPlugin implements HasPluginSettings, Plugin {" to
have the "{" on a new line, and do the same for each function declaration).
Ensure all method-level braces and the class-level brace follow this style
consistently to resolve class_definition, braces_position, and
single_class_elements lint errors.
| class ChatwootPluginProvider extends ServiceProvider { | ||
| public function boot(): void { |
There was a problem hiding this comment.
Fix Pint style: opening braces must be on their own line.
The Lint pipeline is failing (class_definition, brace). PSR-12/Pint requires class and method opening braces on the next line.
🎨 Proposed fix
-class ChatwootPluginProvider extends ServiceProvider {
- public function boot(): void {
+class ChatwootPluginProvider extends ServiceProvider
+{
+ public function boot(): void
+ {📝 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.
| class ChatwootPluginProvider extends ServiceProvider { | |
| public function boot(): void { | |
| class ChatwootPluginProvider extends ServiceProvider | |
| { | |
| public function boot(): void | |
| { |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@chatwoot/src/Providers/ChatwootPluginProvider.php` around lines 10 - 11, The
class and method opening braces are on the same line which violates PSR-12/Pint;
update the class ChatwootPluginProvider and its method boot() so each opening
brace is placed on the following line (i.e., put the "{" for class
ChatwootPluginProvider on its own line and likewise put the "{" for public
function boot() on the next line) to satisfy the class_definition and brace lint
rules.
| fn () => Blade::render(<<<'HTML' | ||
| <!--Start of Chatwoot Script--> | ||
| <script type="text/javascript"> | ||
| (function(d,t) { | ||
| var BASE_URL="{{ $baseUrl }}"; | ||
| var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; | ||
| g.src=BASE_URL+"/packs/js/sdk.js"; | ||
| g.defer = true; | ||
| g.async = true; | ||
| s.parentNode.insertBefore(g,s); | ||
| g.onload=function(){ | ||
| window.chatwootSDK.run({ | ||
| websiteToken: '{{ $websiteToken }}', | ||
| baseUrl: BASE_URL | ||
| }) | ||
| } | ||
| })(document,"script"); | ||
| </script> | ||
| <!--End of Chatwoot Script--> | ||
| HTML, [ | ||
| 'baseUrl' => $baseUrl, | ||
| 'websiteToken' => $websiteToken, | ||
| ]) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Laravel Blade Js::from helper for safely embedding PHP values into JavaScript
💡 Result:
The Laravel Blade Js::from helper, part of Illuminate\Support\Js, safely embeds PHP values into JavaScript by converting them to properly escaped JavaScript expressions. It handles objects and arrays by base64-encoding JSON and wrapping in JSON.parse(atob('...')), strings with single quotes and escapes, and other types via json_encode. Use it in Blade templates as {{ Js::from($value) }} or {{ \Illuminate\Support\Js::from($value) }}. Example: <script> var app = {{ Js::from($array) }}; </script> This ensures safe inclusion within HTML attributes or scripts, preventing XSS from unescaped JSON. Recent Laravel versions (e.g., 13.x) include a Js facade alias for convenience. There's also an undocumented @js($value) Blade directive that wraps Js::from. Official docs confirm: "The from method accepts the same arguments as PHP's json_encode function; however, it will ensure that the resulting JSON has been properly escaped for inclusion within HTML quotes." Only use with existing variables, not complex expressions.
Citations:
- 1: https://laravel.com/docs/13.x/blade
- 2: https://api.laravel.com/docs/10.x/Illuminate/Support/Js.html
- 3: [8.x] Add js() helper laravel/framework#39389
JS-escape interpolated values in the injected <script>.
{{ $websiteToken }} and {{ $baseUrl }} are HTML-escaped by Blade, but they're rendered inside JavaScript string literals. Values containing ', \, newline, or </script> can break out of the JS context and execute arbitrary code. Use Illuminate\Support\Js::from() (or the @js directive) to safely serialize these values for JavaScript.
🔒 Proposed fix using Js::from()
<script type="text/javascript">
(function(d,t) {
- var BASE_URL="{{ $baseUrl }}";
+ var BASE_URL={!! \Illuminate\Support\Js::from($baseUrl) !!};
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
- g.src=BASE_URL+"/packs/js/sdk.js";
+ g.src=BASE_URL.replace(/\/$/, "")+"/packs/js/sdk.js";
g.defer = true;
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
- websiteToken: '{{ $websiteToken }}',
+ websiteToken: {!! \Illuminate\Support\Js::from($websiteToken) !!},
baseUrl: BASE_URL
})
}
})(document,"script");
</script>
HTML, [
- 'baseUrl' => $baseUrl,
- 'websiteToken' => $websiteToken,
+ 'baseUrlJs' => \Illuminate\Support\Js::from($baseUrl),
+ 'websiteTokenJs' => \Illuminate\Support\Js::from($websiteToken),
])(The replace(/\/$/, "") also defends against a trailing slash in the base URL yielding …//packs/js/sdk.js.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@chatwoot/src/Providers/ChatwootPluginProvider.php` around lines 18 - 40, The
injected script currently interpolates Blade-escaped strings directly into JS
string literals causing possible JS injection; update the Blade::render call to
serialize the values with Illuminate\Support\Js::from() (or the `@js` directive)
so baseUrl and websiteToken are safely JS-encoded before being used in the
script where window.chatwootSDK.run is called; also normalize baseUrl to remove
a trailing slash (so the computed g.src = baseUrl + "/packs/js/sdk.js" never
produces a double slash) and reference the serialized values (Js::from($baseUrl)
and Js::from($websiteToken)) instead of raw {{ $baseUrl }} / {{ $websiteToken }}
inside the Blade::render template.
|
Thanks, but new plugins by the community won't be accepted. |
Add Chatwoot plugin and provider
Summary by CodeRabbit