From b992cde35769899e4ee99fcdb8a096dcfd8868d4 Mon Sep 17 00:00:00 2001
From: Jonathan Treffler <mail@jonathan-treffler.de>
Date: Fri, 5 Apr 2024 20:26:15 +0200
Subject: [PATCH] app init; implemented service detection

---
 .vscode/launch.json                     |  38 +++++++++
 appinfo/info.xml                        |  18 ++++
 appinfo/routes.php                      |  11 +++
 lib/AppInfo/Application.php             |  30 +++++++
 lib/Dav/ServiceDetectionPlugin.php      | 106 ++++++++++++++++++++++++
 lib/Event/RegisterTransportsEvent.php   |  28 +++++++
 lib/Listener/SabrePluginAddListener.php |  25 ++++++
 lib/PushTransports/WebPushTransport.php |  11 +++
 lib/Transport/Transport.php             |  17 ++++
 lib/Transport/TransportManager.php      |  38 +++++++++
 10 files changed, 322 insertions(+)
 create mode 100644 .vscode/launch.json
 create mode 100644 appinfo/info.xml
 create mode 100644 appinfo/routes.php
 create mode 100644 lib/AppInfo/Application.php
 create mode 100644 lib/Dav/ServiceDetectionPlugin.php
 create mode 100644 lib/Event/RegisterTransportsEvent.php
 create mode 100644 lib/Listener/SabrePluginAddListener.php
 create mode 100644 lib/PushTransports/WebPushTransport.php
 create mode 100644 lib/Transport/Transport.php
 create mode 100644 lib/Transport/TransportManager.php

diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..317d206
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,38 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch built-in server and debug",
+            "type": "php",
+            "request": "launch",
+            "runtimeArgs": [
+                "-S",
+                "localhost:8000",
+                "-t",
+                "."
+            ],
+            "port": 9003,
+            "serverReadyAction": {
+                "action": "openExternally"
+            }
+        },
+        {
+            "name": "Debug current script in console",
+            "type": "php",
+            "request": "launch",
+            "program": "${file}",
+            "cwd": "${fileDirname}",
+            "externalConsole": false,
+            "port": 9003
+        },
+        {
+            "name": "Listen for Xdebug",
+            "type": "php",
+            "request": "launch",
+            "port": 9003
+        }
+    ]
+}
\ No newline at end of file
diff --git a/appinfo/info.xml b/appinfo/info.xml
new file mode 100644
index 0000000..722063e
--- /dev/null
+++ b/appinfo/info.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+      xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
+    <id>dav_push</id>
+    <name>DAV Push</name>
+    <summary>Implements the DAV Push specification</summary>
+    <description><![CDATA[This app allows you to send push notifications from your Nextcloud server to different clients, including mobile devices, desktop computers, and web browsers.]]></description>
+    <version>0.0.1</version>
+    <licence>agpl</licence>
+    <author mail="info@bitfire.at" homepage="https://github.com/bitfireAT/webdav-push">bitfire web engineering</author>
+    <author mail="mail@jonathan-treffler.de">Jonathan Treffler</author>
+    <namespace>DavPush</namespace>
+    <category>tools</category>
+    <bugs>https://github.com/bitfireAT/nc-ext-caldav-carddav-push/issues</bugs>
+    <dependencies>
+        <nextcloud min-version="28" max-version="28"/>
+    </dependencies>
+</info>
\ No newline at end of file
diff --git a/appinfo/routes.php b/appinfo/routes.php
new file mode 100644
index 0000000..b9eca4d
--- /dev/null
+++ b/appinfo/routes.php
@@ -0,0 +1,11 @@
+<?php
+declare(strict_types=1);
+// SPDX-FileCopyrightText: bitfire web engineering GmbH <info@bitfire.at>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+return [
+        'resources' => [
+        ],
+        'routes' => [
+        ]
+];
\ No newline at end of file
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
new file mode 100644
index 0000000..43fabd5
--- /dev/null
+++ b/lib/AppInfo/Application.php
@@ -0,0 +1,30 @@
+<?php
+
+// SPDX-FileCopyrightText: bitfire web engineering GmbH <info@bitfire.at>
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace OCA\DavPush\AppInfo;
+
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\AppFramework\Bootstrap\IBootContext;
+
+use OCA\DAV\Events\SabrePluginAddEvent;
+
+use OCA\DavPush\Listener\SabrePluginAddListener;
+
+class Application extends App implements IBootstrap {
+	public const APP_ID = 'dav_push';
+
+	public function __construct() {
+		parent::__construct(self::APP_ID);
+	}
+
+    public function register(IRegistrationContext $context): void {
+        $context->registerEventListener(SabrePluginAddEvent::class, SabrePluginAddListener::class);
+    }
+
+    public function boot(IBootContext $context): void {
+    }
+}
\ No newline at end of file
diff --git a/lib/Dav/ServiceDetectionPlugin.php b/lib/Dav/ServiceDetectionPlugin.php
new file mode 100644
index 0000000..c5a4da6
--- /dev/null
+++ b/lib/Dav/ServiceDetectionPlugin.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2024 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DavPush\Dav;
+
+use OCP\IUser;
+use OCP\IUserSession;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCA\DAV\Connector\Sabre\Node;
+use OCA\DavPush\Transport\TransportManager;
+
+use Sabre\DAV\INode;
+use Sabre\DAV\PropFind;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+
+class ServiceDetectionPlugin extends ServerPlugin {
+
+	public const PUSH_PREFIX = '{DAV:Push}';
+    public const PROPERTY_PUSH_TRANSPORTS = self::PUSH_PREFIX . 'push-transports';
+	public const PROPERTY_PUSH_TOPIC = self::PUSH_PREFIX . 'topic';
+
+
+	public function __construct(
+		private IUserSession $userSession,
+		private TransportManager $transportManager,
+	) {
+	}
+
+	public function initialize(Server $server): void {
+		$server->on('propFind', [$this, 'propFind']);
+	}
+
+	public function propFind(PropFind $propFind, INode $node) {
+		if (count(array_intersect([self::PROPERTY_PUSH_TRANSPORTS, self::PROPERTY_PUSH_TOPIC], $propFind->getRequestedProperties())) == 0) {
+			return;
+		}
+
+		//if (!($node instanceof Node)) {
+		//	return;
+		//}
+
+		$propFind->handle(
+			self::PROPERTY_PUSH_TRANSPORTS,
+			function () use ($node) {
+				//$user = $this->userSession->getUser();
+				//if (!($user instanceof IUser)) {
+				//	return [];
+				//}
+
+				$transports = $this->transportManager->getTransports();
+				
+				$result = [];
+				
+				foreach($transports as $transport) {
+					$result[] = [
+						(self::PUSH_PREFIX . "transport") => [
+							(self::PUSH_PREFIX . $transport->getId()) => $transport->getAdditionalInformation(),
+						]
+					];
+				}
+
+				//throw new \Exception( "\$result = " . json_encode($result) );
+
+				return $result;
+			},
+		);
+
+        $propFind->handle(
+			self::PROPERTY_PUSH_TOPIC,
+			//function () use ($node) {
+				//$user = $this->userSession->getUser();
+				//if (!($user instanceof IUser)) {
+				//	return [];
+				//}
+
+			//	return "test-return-push";
+			//},
+            "test-return-push-topic"
+		);
+	}
+}
\ No newline at end of file
diff --git a/lib/Event/RegisterTransportsEvent.php b/lib/Event/RegisterTransportsEvent.php
new file mode 100644
index 0000000..34a3db9
--- /dev/null
+++ b/lib/Event/RegisterTransportsEvent.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DavPush\Event;
+
+use OCP\EventDispatcher\Event;
+
+use OCA\DavPush\Transport\TransportManager;
+
+/**
+ * This event is triggered during the initialization of DAV Push.
+ * Use it to register external push transports.
+ */
+class RegisterTransportsEvent extends Event {
+
+	/** @var TransportManager */
+	private $transportManager;
+
+	public function __construct(TransportManager $transportManager) {
+		parent::__construct();
+		$this->transportManager = $transportManager;
+	}
+
+	public function getTransportManager(): TransportManager {
+		return $this->transportManager;
+	}
+}
\ No newline at end of file
diff --git a/lib/Listener/SabrePluginAddListener.php b/lib/Listener/SabrePluginAddListener.php
new file mode 100644
index 0000000..ef2876d
--- /dev/null
+++ b/lib/Listener/SabrePluginAddListener.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DavPush\Listener;
+
+use OCA\DAV\Events\SabrePluginAddEvent;
+use OCA\DavPush\Dav\ServiceDetectionPlugin;
+
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+
+use Psr\Container\ContainerInterface;
+
+class SabrePluginAddListener implements IEventListener {
+	public function __construct(private ContainerInterface $container) {}
+
+	public function handle(Event $event): void {
+		if ($event instanceof SabrePluginAddEvent) {
+            $serviceDetectionPlugin = $this->container->get(ServiceDetectionPlugin::class);
+
+            $event->getServer()->addPlugin($serviceDetectionPlugin);
+        }
+	}
+}
\ No newline at end of file
diff --git a/lib/PushTransports/WebPushTransport.php b/lib/PushTransports/WebPushTransport.php
new file mode 100644
index 0000000..6a8333a
--- /dev/null
+++ b/lib/PushTransports/WebPushTransport.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DavPush\PushTransports;
+
+use OCA\DavPush\Transport\Transport;
+
+class WebPushTransport extends Transport {
+    protected $id = "web-push";
+}
\ No newline at end of file
diff --git a/lib/Transport/Transport.php b/lib/Transport/Transport.php
new file mode 100644
index 0000000..a0cc6ff
--- /dev/null
+++ b/lib/Transport/Transport.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DavPush\Transport;
+
+abstract class Transport {
+    protected $id;
+
+    public function getId() {
+        return $this->id;
+    }
+
+    public function getAdditionalInformation() {
+        return [];
+    }
+}
\ No newline at end of file
diff --git a/lib/Transport/TransportManager.php b/lib/Transport/TransportManager.php
new file mode 100644
index 0000000..d82fb6e
--- /dev/null
+++ b/lib/Transport/TransportManager.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace OCA\DavPush\Transport;
+
+use OCP\EventDispatcher\IEventDispatcher;
+
+use OCA\DavPush\Event\RegisterTransportsEvent;
+use OCA\DavPush\PushTransports\WebPushTransport;
+
+class TransportManager {
+    /**
+	 * @var Transport[]
+	 */
+	private array $transports = [];
+
+    public function __construct(IEventDispatcher $dispatcher) {
+        // register integrated transports
+        $this->registerTransport(new WebPushTransport());
+
+        // register transports provided by other apps
+        $event = new RegisterTransportsEvent($this);
+        $dispatcher->dispatchTyped($event);
+    }
+
+    /**
+     * @return Transport[]
+     */
+    public function getTransports(): array {
+        return $this->transports;
+    }
+
+    public function registerTransport(Transport $transport): self {
+		$this->transports[] = $transport;
+		return $this;
+	}
+}
\ No newline at end of file