Skip to content

Commit 059c493

Browse files
authored
Fix livewire event forwarding (#9)
* rewrite livewire event forwarding * add tests * Build plugin * tidy --------- Co-authored-by: gwleuverink <17123491+gwleuverink@users.noreply.github.com>
1 parent 0e6ec8a commit 059c493

File tree

6 files changed

+197
-65
lines changed

6 files changed

+197
-65
lines changed

resources/electron/electron-plugin/dist/preload/index.mjs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,11 @@ ipcRenderer.on('log', (event, { level, message, context }) => {
2929
});
3030
ipcRenderer.on('native-event', (event, data) => {
3131
data.event = data.event.replace(/^(\\)+/, '');
32-
if (window.Livewire) {
33-
window.Livewire.dispatch('native:' + data.event, data.payload);
34-
}
35-
if (window.livewire) {
36-
window.livewire.components.components().forEach(component => {
37-
if (Array.isArray(component.listeners)) {
38-
component.listeners.forEach(event => {
39-
if (event.startsWith('native')) {
40-
let event_parts = event.split(/(native:|native-)|:|,/);
41-
if (event_parts[1] == 'native:') {
42-
event_parts.splice(2, 0, 'private', undefined, 'nativephp', undefined);
43-
}
44-
let [s1, signature, channel_type, s2, channel, s3, event_name,] = event_parts;
45-
if (data.event === event_name) {
46-
window.livewire.emit(event, data.payload);
47-
}
48-
}
49-
});
50-
}
51-
});
52-
}
32+
window.postMessage({
33+
type: 'native-event',
34+
event: data.event,
35+
payload: data.payload
36+
}, '*');
5337
});
5438
contextBridge.exposeInMainWorld('native:initialized', (function () {
5539
window.dispatchEvent(new CustomEvent('native:init'));

resources/electron/electron-plugin/src/preload/index.mts

Lines changed: 11 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -43,49 +43,16 @@ ipcRenderer.on('log', (event, {level, message, context}) => {
4343
// -------------------------------------------------------------------
4444
ipcRenderer.on('native-event', (event, data) => {
4545

46-
// Strip leading slashes
47-
data.event = data.event.replace(/^(\\)+/, '');
48-
49-
// add support for livewire 3
50-
// @ts-ignore
51-
if (window.Livewire) {
52-
// @ts-ignore
53-
window.Livewire.dispatch('native:' + data.event, data.payload);
54-
}
55-
56-
// add support for livewire 2
57-
// @ts-ignore
58-
if (window.livewire) {
59-
// @ts-ignore
60-
window.livewire.components.components().forEach(component => {
61-
if (Array.isArray(component.listeners)) {
62-
component.listeners.forEach(event => {
63-
if (event.startsWith('native')) {
64-
let event_parts = event.split(/(native:|native-)|:|,/)
65-
66-
if (event_parts[1] == 'native:') {
67-
event_parts.splice(2, 0, 'private', undefined, 'nativephp', undefined)
68-
}
69-
70-
let [
71-
s1,
72-
signature,
73-
channel_type,
74-
s2,
75-
channel,
76-
s3,
77-
event_name,
78-
] = event_parts
79-
80-
if (data.event === event_name) {
81-
// @ts-ignore
82-
window.livewire.emit(event, data.payload)
83-
}
84-
}
85-
})
86-
}
87-
})
88-
}
46+
// Strip leading slashes
47+
data.event = data.event.replace(/^(\\)+/, '');
48+
49+
// Forward event to renderer context
50+
// Handler injected via Events\LivewireDispatcher
51+
window.postMessage({
52+
type: 'native-event',
53+
event: data.event,
54+
payload: data.payload
55+
}, '*');
8956
})
9057

9158
// -------------------------------------------------------------------
@@ -100,7 +67,7 @@ contextBridge.exposeInMainWorld('native:initialized', (function() {
10067
// It's recommended to use window.postMessage & dispatch an
10168
// event from the renderer itself, but we're loading webcontent
10269
// from localhost. We don't have a renderer process we can access.
103-
// Though this is hacky it works well and is the quickest way to do this
70+
// Though this is hacky it works well and is the simplest way to do this
10471
// without sprinkling additional logic all over the place.
10572

10673
window.dispatchEvent(new CustomEvent('native:init'));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
window.addEventListener("message", (event) => {
2+
if (event.data.type === "native-event") {
3+
const { event: eventName, payload } = event.data;
4+
5+
LivewireDispatcher.handle(eventName, payload);
6+
}
7+
});
8+
9+
const LivewireDispatcher = {
10+
handle: function (eventName, payload) {
11+
// Livewire 3
12+
if (window.Livewire) {
13+
window.Livewire.dispatch("native:" + eventName, payload);
14+
}
15+
16+
// Livewire 2
17+
if (window.livewire) {
18+
window.livewire.components.components().forEach((component) => {
19+
if (Array.isArray(component.listeners)) {
20+
component.listeners.forEach((event) => {
21+
22+
if (event.startsWith("native")) {
23+
let event_parts = event.split(
24+
/(native:|native-)|:|,/,
25+
);
26+
27+
if (event_parts[1] == "native:") {
28+
event_parts.splice(2, 0, "private", undefined, "nativephp", undefined);
29+
}
30+
31+
let [s1, signature, channel_type, s2, channel, s3, event_name] = event_parts;
32+
33+
if (eventName === event_name) {
34+
// @ts-ignore
35+
window.livewire.emit(event, payload);
36+
}
37+
}
38+
});
39+
}
40+
});
41+
}
42+
},
43+
};

src/Drivers/Electron/ElectronServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Native\Desktop\Drivers\Electron\Commands\RunCommand;
1212
use Native\Desktop\Drivers\Electron\Commands\ServeCommand;
1313
use Native\Desktop\Drivers\Electron\Updater\UpdaterManager;
14+
use Native\Desktop\Events\LivewireDispatcher;
1415
use Native\Desktop\Support\Composer;
1516
use Spatie\LaravelPackageTools\Package;
1617
use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -58,4 +59,9 @@ public function packageRegistered(): void
5859
);
5960
});
6061
}
62+
63+
public function packageBooted(): void
64+
{
65+
app(LivewireDispatcher::class)->register();
66+
}
6167
}

src/Events/LivewireDispatcher.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Native\Desktop\Events;
4+
5+
use Illuminate\Foundation\Http\Events\RequestHandled;
6+
use Illuminate\Support\Facades\Event;
7+
use Native\Desktop\Drivers\Electron\ElectronServiceProvider;
8+
9+
class LivewireDispatcher
10+
{
11+
public function register(): void
12+
{
13+
Event::listen(function (RequestHandled $event) {
14+
$this->handle($event);
15+
});
16+
}
17+
18+
/** Injects assets inside every full-page response */
19+
public function handle(RequestHandled $handled)
20+
{
21+
$identifier = 'NativePHP Livewire Dispatcher';
22+
$html = $handled->response->getContent();
23+
$originalContent = $handled->response->original;
24+
25+
if (! $handled->response->isSuccessful()) {
26+
return;
27+
}
28+
29+
// Skip if request doesn't return a full page
30+
if (! str_contains($html, '</html>')) {
31+
return;
32+
}
33+
34+
// Skip when included before
35+
if (str_contains($html, "<!--[{$identifier}]-->")) {
36+
return;
37+
}
38+
39+
// Get the JS dispatcher script
40+
$javascript = file_get_contents(
41+
ElectronServiceProvider::electronPath('electron-plugin/src/preload/livewire-dispatcher.js')
42+
);
43+
44+
$handled->response->setContent(
45+
$this->inject($html, <<< HTML
46+
<!--[{$identifier}]-->
47+
<script type="module">
48+
{$javascript}
49+
</script>
50+
HTML)
51+
);
52+
53+
$handled->response->original = $originalContent;
54+
}
55+
56+
/** Injects assets into given html string (taken from Livewire's injection mechanism) */
57+
protected function inject(string $html, string $assets): string
58+
{
59+
$html = str($html);
60+
$assets = PHP_EOL.$assets.PHP_EOL;
61+
62+
if ($html->test('/<\s*\/\s*head\s*>/i')) {
63+
return $html
64+
->replaceMatches('/(<\s*\/\s*head\s*>)/i', $assets.'$1')
65+
->toString();
66+
}
67+
68+
return $html
69+
->replaceMatches('/(<\s*\/\s*html\s*>)/i', $assets.'$1')
70+
->toString();
71+
}
72+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
it('injects dispatcher into response', function () {
4+
5+
Route::get('test-inject-in-response', fn () => '<html><head></head></html>');
6+
7+
$this->get('test-inject-in-response')
8+
->assertOk()
9+
->assertSee('<!--[NativePHP Livewire Dispatcher]-->', false);
10+
});
11+
12+
it('injects dispatcher into head tag', function () {
13+
14+
Route::get('test-inject-in-response', fn () => '<html><head></head></html>');
15+
16+
$expected = <<< 'HTML'
17+
<html><head>
18+
<!--[NativePHP Livewire Dispatcher]-->
19+
HTML;
20+
21+
$this->get('test-inject-in-response')
22+
->assertOk()
23+
->assertSee($expected, false);
24+
});
25+
26+
it('injects dispatcher into html body when no head tag is present', function () {
27+
28+
Route::get('test-inject-in-response', fn () => '<html></html>');
29+
30+
$expected = <<< 'HTML'
31+
<html>
32+
<!--[NativePHP Livewire Dispatcher]-->
33+
HTML;
34+
35+
$this->get('test-inject-in-response')
36+
->assertOk()
37+
->assertSee($expected, false);
38+
});
39+
40+
it('injects dispatcher into the end of the html body when no head tag is present', function () {
41+
42+
Route::get('test-inject-in-response', fn () => '<html><p>Hello World</p></html>');
43+
44+
$expected = <<< 'HTML'
45+
<html><p>Hello World</p>
46+
<!--[NativePHP Livewire Dispatcher]-->
47+
HTML;
48+
49+
$this->get('test-inject-in-response')
50+
->assertOk()
51+
->assertSee($expected, false);
52+
});
53+
54+
it('doesnt inject dispatcher into responses without a closing html tag', function () {
55+
Route::get('test-inject-in-response', fn () => 'OK');
56+
57+
$this->get('test-inject-in-response')
58+
->assertOk()
59+
->assertDontSee('<!--[NativePHP Livewire Dispatcher]-->', false);
60+
});

0 commit comments

Comments
 (0)