Skip to content

Commit

Permalink
feature symfony#49 [Notify] Add Notify bundle (mtarld)
Browse files Browse the repository at this point in the history
This PR was merged into the 2.x branch.

Discussion
----------

[Notify] Add Notify bundle

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| Tickets       |
| License       | MIT

In Symfony 5.3, Notifier was shipped with a Mercure bridge (symfony/symfony#39342)

Then, with a simple `$this->notifier->send(new Notification('My message', ['chat/mercure']));` (and a running Mercure server), it'll be easy to create "Server-Sent-Events".

Therefore, the Notify bundle idea is to listen to these events using JavaScript event sourcing and convert them as native HTML5 notifications (using the `{{ stream_notifications() }}` Twig function).

(The "Notify" name was the first that came to my mind a could/should be challenged)

Commits
-------

e02a362 [Notify] Add Notify library
  • Loading branch information
weaverryan committed May 23, 2022
2 parents 6b629c5 + e02a362 commit 9b877d4
Show file tree
Hide file tree
Showing 26 changed files with 1,054 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,23 @@ jobs:
working-directory: src/LiveComponent
run: php vendor/bin/simple-phpunit

tests-php81-high-deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- run: php .github/build-packages.php

- name: Notify Dependencies
uses: ramsey/composer-install@v2
with:
working-directory: src/Notify
- name: Notify Tests
working-directory: src/Notify
run: php vendor/bin/simple-phpunit

tests-js:
runs-on: ubuntu-latest
steps:
Expand Down
5 changes: 5 additions & 0 deletions src/Notify/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/Resources/assets/test export-ignore
/Tests export-ignore
4 changes: 4 additions & 0 deletions src/Notify/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor/
.phpunit.result.cache
.php_cs.cache
composer.lock
3 changes: 3 additions & 0 deletions src/Notify/.symfony-bundle.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
branches: ["2.x"]
maintained_branches: ["2.x"]
doc_dir: "Resources/doc"
39 changes: 39 additions & 0 deletions src/Notify/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Notify\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* @author Mathias Arlaud <[email protected]>
*
* @internal
*/
final class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('notify');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->children()
->scalarNode('mercure_hub')
->info('Mercube hub service id')
->defaultValue('mercure.hub.default')
->end()
->end()
;

return $treeBuilder;
}
}
44 changes: 44 additions & 0 deletions src/Notify/DependencyInjection/NotifyExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Notify\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension;
use Symfony\UX\Notify\Twig\NotifyExtension as TwigNotifyExtension;
use Symfony\UX\Notify\Twig\NotifyRuntime;

/**
* @author Mathias Arlaud <[email protected]>
*
* @internal
*/
final class NotifyExtension extends ConfigurableExtension
{
/**
* {@inheritdoc}
*/
public function loadInternal(array $config, ContainerBuilder $container)
{
$container->register('notify.twig_extension', TwigNotifyExtension::class)
->addTag('twig.extension')
;

$container->register('notify.twig_runtime', NotifyRuntime::class)
->setArguments([
new Reference($config['mercure_hub']),
new Reference('webpack_encore.twig_stimulus_extension'),
])
->addTag('twig.runtime')
;
}
}
19 changes: 19 additions & 0 deletions src/Notify/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2020-2021 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
24 changes: 24 additions & 0 deletions src/Notify/NotifyBundle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Notify;

use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
* @author Mathias Arlaud <[email protected]>
*
* @final
* @experimental
*/
class NotifyBundle extends Bundle
{
}
18 changes: 18 additions & 0 deletions src/Notify/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Symfony UX Notify

Symfony UX Notify is a Symfony bundle integrating server-sent
[native notifications](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API)
in Symfony applications using [Mercure](https://mercure.rocks/).
It is part of [the Symfony UX initiative](https://symfony.com/ux).

![Example of a native notification](https://github.com/symfony/ux/blob/2.x/src/Notify/Resources/doc/native-notification-example.png?raw=true)

**This repository is a READ-ONLY sub-tree split**. See
https://github.com/symfony/ux to create issues or submit pull requests.

## Resources

- [Documentation](https://symfony.com/bundles/ux-notify/current/index.html)
- [Report issues](https://github.com/symfony/ux/issues) and
[send Pull Requests](https://github.com/symfony/ux/pulls)
in the [main Symfony UX repository](https://github.com/symfony/ux)
63 changes: 63 additions & 0 deletions src/Notify/Resources/assets/dist/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Controller } from '@hotwired/stimulus';

class default_1 extends Controller {
constructor() {
super(...arguments);
this.eventSources = [];
}
initialize() {
const errorMessages = [];
if (!this.hasHubValue)
errorMessages.push('A "hub" value pointing to the Mercure hub must be provided.');
if (!this.hasTopicsValue)
errorMessages.push('A "topics" value must be provided.');
if (errorMessages.length)
throw new Error(errorMessages.join(' '));
this.eventSources = this.topicsValue.map((topic) => {
const u = new URL(this.hubValue);
u.searchParams.append('topic', topic);
return new EventSource(u);
});
}
connect() {
if (!('Notification' in window)) {
console.warn('This browser does not support desktop notifications.');
return;
}
this.eventSources.forEach((eventSource) => {
eventSource.addEventListener('message', (event) => this._notify(JSON.parse(event.data).summary));
});
this._dispatchEvent('notify:connect', { eventSources: this.eventSources });
}
disconnect() {
this.eventSources.forEach((eventSource) => {
eventSource.removeEventListener('message', this._notify);
eventSource.close();
});
this.eventSources = [];
}
_notify(content) {
if (!content)
return;
if ('granted' === Notification.permission) {
new Notification(content);
return;
}
if ('denied' !== Notification.permission) {
Notification.requestPermission().then((permission) => {
if ('granted' === permission) {
new Notification(content);
}
});
}
}
_dispatchEvent(name, payload) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
}
}
default_1.values = {
hub: String,
topics: Array,
};

export { default_1 as default };
1 change: 1 addition & 0 deletions src/Notify/Resources/assets/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../../../../jest.config.js');
22 changes: 22 additions & 0 deletions src/Notify/Resources/assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@symfony/ux-notify",
"description": "Native notification integration for Symfony using Mercure",
"license": "MIT",
"version": "1.0.0",
"symfony": {
"controllers": {
"notify": {
"main": "dist/controller.js",
"webpackMode": "eager",
"fetch": "eager",
"enabled": true
}
}
},
"peerDependencies": {
"@hotwired/stimulus": "^3.0.0"
},
"devDependencies": {
"@hotwired/stimulus": "^3.0.0"
}
}
85 changes: 85 additions & 0 deletions src/Notify/Resources/assets/src/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

'use strict';

import { Controller } from '@hotwired/stimulus';

/**
* @author Mathias Arlaud <[email protected]>
*/
export default class extends Controller {
static values = {
hub: String,
topics: Array,
};

eventSources: Array<EventSource> = [];

initialize() {
const errorMessages: Array<string> = [];

if (!this.hasHubValue) errorMessages.push('A "hub" value pointing to the Mercure hub must be provided.');
if (!this.hasTopicsValue) errorMessages.push('A "topics" value must be provided.');

if (errorMessages.length) throw new Error(errorMessages.join(' '));

this.eventSources = this.topicsValue.map((topic) => {
const u = new URL(this.hubValue);
u.searchParams.append('topic', topic);

return new EventSource(u);
});
}

connect() {
if (!('Notification' in window)) {
console.warn('This browser does not support desktop notifications.');

return;
}

this.eventSources.forEach((eventSource) => {
eventSource.addEventListener('message', (event) => this._notify(JSON.parse(event.data).summary));
});

this._dispatchEvent('notify:connect', { eventSources: this.eventSources });
}

disconnect() {
this.eventSources.forEach((eventSource) => {
eventSource.removeEventListener('message', this._notify);
eventSource.close();
});

this.eventSources = [];
}

_notify(content: string | undefined) {
if (!content) return;

if ('granted' === Notification.permission) {
new Notification(content);

return;
}

if ('denied' !== Notification.permission) {
Notification.requestPermission().then((permission) => {
if ('granted' === permission) {
new Notification(content);
}
});
}
}

_dispatchEvent(name: string, payload: any) {
this.element.dispatchEvent(new CustomEvent(name, { detail: payload, bubbles: true }));
}
}
Loading

0 comments on commit 9b877d4

Please sign in to comment.