Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

use OCA\Whiteboard\Controller\ExAppController;
use OCA\Whiteboard\Controller\JWTController;
use OCA\Whiteboard\Controller\SettingsController;
use OCA\Whiteboard\Controller\WhiteboardController;
Expand All @@ -21,5 +22,7 @@
['name' => 'Whiteboard#show', 'url' => '{fileId}', 'verb' => 'GET'],
/** @see SettingsController::update() */
['name' => 'Settings#update', 'url' => 'settings', 'verb' => 'POST'],
/** @see ExAppController::updateSettings() */
['name' => 'ExApp#updateSettings', 'url' => 'ex_app/settings', 'verb' => 'POST'],
]
];
27 changes: 27 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

namespace OCA\Whiteboard\AppInfo;

use OCA\AppAPI\Middleware\AppAPIAuthMiddleware;
use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Viewer\Event\LoadViewer;
use OCA\Whiteboard\Listener\AddContentSecurityPolicyListener;
use OCA\Whiteboard\Listener\BeforeTemplateRenderedListener;
use OCA\Whiteboard\Listener\LoadViewerListener;
use OCA\Whiteboard\Listener\RegisterTemplateCreatorListener;
use OCA\Whiteboard\Service\ExAppService;
use OCA\Whiteboard\Settings\SetupCheck;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
Expand All @@ -26,6 +28,9 @@
use OCP\IL10N;
use OCP\Security\CSP\AddContentSecurityPolicyEvent;
use OCP\Util;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;

/**
* @psalm-suppress UndefinedClass
Expand All @@ -38,6 +43,10 @@ public function __construct(array $params = []) {
parent::__construct(self::APP_ID, $params);
}

/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function register(IRegistrationContext $context): void {
include_once __DIR__ . '/../../vendor/autoload.php';

Expand All @@ -46,8 +55,16 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(RegisterTemplateCreatorEvent::class, RegisterTemplateCreatorListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerSetupCheck(SetupCheck::class);

if (class_exists(AppAPIAuthMiddleware::class) && $this->getExAppService()->isWhiteboardWebsocketEnabled()) {
$context->registerMiddleware(AppAPIAuthMiddleware::class);
}
}

/**
* @throws ContainerExceptionInterface
* @throws Throwable
*/
public function boot(IBootContext $context): void {
[$major] = Util::getVersion();
if ($major < 30) {
Expand All @@ -57,5 +74,15 @@ public function boot(IBootContext $context): void {
});
});
}

$this->getExAppService()->initFrontendState();
}

/**
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function getExAppService(): ExAppService {
return $this->getContainer()->get(ExAppService::class);
}
}
16 changes: 16 additions & 0 deletions lib/Consts/ExAppConsts.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace OCA\Whiteboard\Consts;

final class ExAppConsts {
public const APP_API_ID = 'app_api';
public const WHITEBOARD_EX_APP_ID = 'whiteboard_websocket';
public const WHITEBOARD_EX_APP_ENABLED_KEY = 'isWhiteboardWebsocketEnabled';
}
59 changes: 59 additions & 0 deletions lib/Controller/ExAppController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Controller;

use Exception;
use OCA\AppAPI\Attribute\AppAPIAuth;
use OCA\Whiteboard\Service\ConfigService;
use OCA\Whiteboard\Service\ExceptionService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;

/**
* @psalm-suppress UndefinedClass
* @psalm-suppress MissingDependency
* @psalm-suppress UndefinedAttributeClass
*/
final class ExAppController extends Controller {
public function __construct(
IRequest $request,
private ExceptionService $exceptionService,
private ConfigService $configService,
) {
parent::__construct('whiteboard', $request);
}

#[NoCSRFRequired]
#[PublicPage]
#[AppAPIAuth]
public function updateSettings(): DataResponse {
try {
$serverUrl = $this->request->getParam('serverUrl');
$secret = $this->request->getParam('secret');

if ($serverUrl !== null) {
$this->configService->setCollabBackendUrl($serverUrl);
}

if ($secret !== null) {
$this->configService->setWhiteboardSharedSecret($secret);
}

return new DataResponse([
'message' => 'Settings updated',
]);
} catch (Exception $e) {
return $this->exceptionService->handleException($e);
}
}
}
70 changes: 70 additions & 0 deletions lib/Service/ExAppService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Whiteboard\Service;

use OCA\AppAPI\Service\ExAppService as AppAPIService;
use OCA\Whiteboard\Consts\ExAppConsts;
use OCP\App\IAppManager;
use OCP\AppFramework\Services\IInitialState;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Throwable;

/**
* @psalm-suppress UndefinedClass
* @psalm-suppress MissingDependency
*/
final class ExAppService {
private ?AppAPIService $appAPIService = null;

public function __construct(
private IAppManager $appManager,
private ContainerInterface $container,
private IInitialState $initialState,
private LoggerInterface $logger,
) {
$this->initAppAPIService();
}

private function initAppAPIService(): void {
$isAppAPIEnabled = $this->isAppAPIEnabled();

if (class_exists(AppAPIService::class) && $isAppAPIEnabled) {
try {
$this->appAPIService = $this->container->get(AppAPIService::class);
} catch (Throwable $e) {
$this->logger->error('exApp', [$e->getMessage()]);
}
}
}

private function isAppAPIEnabled(): bool {
return $this->appManager->isEnabledForUser(ExAppConsts::APP_API_ID);
}

public function isExAppEnabled(string $appId): bool {
if ($this->appAPIService === null) {
return false;
}

return $this->appAPIService->getExApp($appId)?->getEnabled() === 1;
}

public function isWhiteboardWebsocketEnabled(): bool {
return $this->isExAppEnabled(ExAppConsts::WHITEBOARD_EX_APP_ID);
}

public function initFrontendState(): void {
$this->initialState->provideInitialState(
ExAppConsts::WHITEBOARD_EX_APP_ENABLED_KEY,
$this->isWhiteboardWebsocketEnabled()
);
}
}
10 changes: 8 additions & 2 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\Whiteboard\Settings;

use OCA\Whiteboard\Service\ConfigService;
use OCA\Whiteboard\Service\ExAppService;
use OCA\Whiteboard\Service\JWTService;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
Expand All @@ -19,6 +20,7 @@ public function __construct(
private IInitialState $initialState,
private ConfigService $configService,
private JWTService $jwtService,
private ExAppService $exAppService,
) {
}

Expand All @@ -41,11 +43,15 @@ public function getForm(): TemplateResponse {
return $response;
}

public function getSection() {
public function getSection(): ?string {
if ($this->exAppService->isWhiteboardWebsocketEnabled()) {
return null;
}

return 'whiteboard';
}

public function getPriority() {
public function getPriority(): int {
return 0;
}
}
2 changes: 2 additions & 0 deletions lib/Settings/Section.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace OCA\Whiteboard\Settings;

use OCA\Whiteboard\Service\ExAppService;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
Expand All @@ -15,6 +16,7 @@ class Section implements IIconSection {
public function __construct(
private IURLGenerator $url,
private IL10N $l10n,
private ExAppService $exAppService,
) {
}

Expand Down
49 changes: 28 additions & 21 deletions src/collaboration/Portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
import type { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types'
import { io, type Socket } from 'socket.io-client'
import type { Collab } from './collab'
import type { AppState, BinaryFiles, Gesture } from '@excalidraw/excalidraw/types/types'
import type {
AppState,
BinaryFiles,
Gesture,
} from '@excalidraw/excalidraw/types/types'
import axios from '@nextcloud/axios'
import { loadState } from '@nextcloud/initial-state'
import { generateUrl } from '@nextcloud/router'
Expand All @@ -24,7 +28,11 @@ export class Portal {
collab: Collab
publicSharingToken: string | null

constructor(roomId: string, collab: Collab, publicSharingToken: string | null) {
constructor(
roomId: string,
collab: Collab,
publicSharingToken: string | null,
) {
this.roomId = roomId
this.collab = collab
this.publicSharingToken = publicSharingToken
Expand All @@ -37,14 +45,15 @@ export class Portal {

const url = new URL(collabBackendUrl)
const path = url.pathname.replace(/\/$/, '') + '/socket.io'
const isWhiteboardWebsocketEnabled = loadState('whiteboard', 'isWhiteboardWebsocketEnabled', false)

const socket = io(url.origin, {
path,
withCredentials: true,
auth: {
token,
},
transports: ['websocket'],
transports: isWhiteboardWebsocketEnabled ? ['polling'] : ['websocket'],
timeout: 10000,
}).connect()

Expand All @@ -66,9 +75,7 @@ export class Portal {
}

handleConnectionError = () => {
alert(
'Failed to connect to the whiteboard server.',
)
alert('Failed to connect to the whiteboard server.')
OCA.Viewer?.close()
}

Expand Down Expand Up @@ -161,29 +168,29 @@ export class Portal {

async refreshJWT(): Promise<string | null> {
try {
let url = generateUrl(`apps/whiteboard/${this.roomId}/token`)
if (this.publicSharingToken) {
let url = generateUrl(`apps/whiteboard/${this.roomId}/token`)
if (this.publicSharingToken) {
url += `?publicSharingToken=${encodeURIComponent(this.publicSharingToken)}`
}
}

const response = await axios.get(url, { withCredentials: true })
const response = await axios.get(url, { withCredentials: true })

const token = response.data.token
const token = response.data.token

console.log('token', token)
console.log('token', token)

if (!token) throw new Error('No token received')
if (!token) throw new Error('No token received')

localStorage.setItem(`jwt-${this.roomId}`, token)
localStorage.setItem(`jwt-${this.roomId}`, token)

return token
return token
} catch (error) {
console.error('Error refreshing JWT:', error)
alert(error.message)
OCA.Viewer?.close()
return null
console.error('Error refreshing JWT:', error)
alert(error.message)
OCA.Viewer?.close()
return null
}
}
}

async _broadcastSocketData(
data: {
Expand Down Expand Up @@ -241,7 +248,7 @@ export class Portal {
}

async sendImageFiles(files: BinaryFiles) {
Object.values(files).forEach(file => {
Object.values(files).forEach((file) => {
this.collab.addFile(file)
this.socket?.emit('image-add', this.roomId, file.id, file)
})
Expand Down
Loading
Loading