Skip to content

Commit

Permalink
feat(call): Direct endpoint to check if call notification should be d…
Browse files Browse the repository at this point in the history
…ismissed

Signed-off-by: Joas Schilling <coding@schilljs.com>
  • Loading branch information
nickvergessen committed Dec 4, 2024
1 parent 46a9649 commit 20eb5b6
Show file tree
Hide file tree
Showing 9 changed files with 662 additions and 0 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes/routesCallController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
'ocs' => [
/** @see \OCA\Talk\Controller\CallController::getPeersForCall() */
['name' => 'Call#getPeersForCall', 'url' => '/api/{apiVersion}/call/{token}', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\CallNotificationController::state() */
['name' => 'CallNotification#state', 'url' => '/api/{apiVersion}/call/{token}/notification', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\CallController::downloadParticipantsForCall() */
['name' => 'Call#downloadParticipantsForCall', 'url' => '/api/{apiVersion}/call/{token}/download', 'verb' => 'GET', 'requirements' => $requirements],
/** @see \OCA\Talk\Controller\CallController::joinCall() */
Expand Down
1 change: 1 addition & 0 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,4 @@
## 21
* `config => conversations => force-passwords` - Whether passwords are enforced for public rooms
* `conversation-creation-password` - Whether the endpoints for creating public conversations or making a conversation public support setting a password
* `call-notification-state-api` - Whether the endpoints exists to checking if a call notification should be dismissed
2 changes: 2 additions & 0 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class Capabilities implements IPublicCapability {
'download-call-participants',
'email-csv-import',
'conversation-creation-password',
'call-notification-state-api',
];

public const CONDITIONAL_FEATURES = [
Expand All @@ -130,6 +131,7 @@ class Capabilities implements IPublicCapability {
'note-to-self',
'archived-conversations-v2',
'chat-summary-api',
'call-notification-state-api',
];

public const LOCAL_CONFIGS = [
Expand Down
66 changes: 66 additions & 0 deletions lib/Controller/CallNotificationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

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

namespace OCA\Talk\Controller;

use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;

class CallNotificationController extends OCSController {
public const CASE_STILL_CURRENT = 0;
public const CASE_ROOM_NOT_FOUND = 1;
public const CASE_MISSED_CALL = 2;
public const CASE_PARTICIPANT_JOINED = 3;


public function __construct(
string $appName,
IRequest $request,
protected ParticipantService $participantService,
protected ?string $userId,
) {
parent::__construct($appName, $request);
}

/**
* Check the expected state of a call notification
*
* Required capability: `call-notification-state-api`
*
* @param string $token Conversation token to check
* @return DataResponse<Http::STATUS_OK|Http::STATUS_CREATED|Http::STATUS_FORBIDDEN|Http::STATUS_NOT_FOUND, null, array{}>
*
* 200: Notification should be kept alive
* 201: Dismiss call notification and show "Missed call"-notification instead
* 403: Not logged in, try again with auth data sent
* 404: Dismiss call notification
*/
#[NoAdminRequired]
#[OpenAPI(tags: ['call'])]
public function state(string $token): DataResponse {
if ($this->userId === null) {
return new DataResponse(null, Http::STATUS_FORBIDDEN);
}

$status = match($this->participantService->checkIfUserIsMissingCall($token, $this->userId)) {
self::CASE_PARTICIPANT_JOINED,
self::CASE_ROOM_NOT_FOUND => Http::STATUS_NOT_FOUND,
self::CASE_MISSED_CALL => Http::STATUS_CREATED,
// self::CASE_STILL_CURRENT,
default => Http::STATUS_OK,
};

return new DataResponse(null, $status);
}
}
55 changes: 55 additions & 0 deletions lib/Service/ParticipantService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCA\Talk\CachePrefix;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Config;
use OCA\Talk\Controller\CallNotificationController;
use OCA\Talk\Events\AAttendeeRemovedEvent;
use OCA\Talk\Events\AParticipantModifiedEvent;
use OCA\Talk\Events\AttendeeRemovedEvent;
Expand Down Expand Up @@ -1601,6 +1602,60 @@ public function getParticipantsInCall(Room $room, int $maxAge = 0): array {
return $this->getParticipantsFromQuery($query, $room);
}

/**
* Do not try to modernize this into using the Room, Participant or other objects.
* This function is called by {@see CallNotificationController::state}
* and mobile as well as desktop clients are basically ddos-ing it, to check
* if the call notification / call screen should be removed.
* @return CallNotificationController::CASE_*
*/
public function checkIfUserIsMissingCall(string $token, string $userId): int {
$query = $this->connection->getQueryBuilder();
$query->select('r.active_since', 'a.last_joined_call', 's.in_call')
->from('talk_rooms', 'r')
->innerJoin(
'r', 'talk_attendees', 'a',
$query->expr()->eq('r.id', 'a.room_id')
)
->leftJoin(
'a', 'talk_sessions', 's',
$query->expr()->andX(
$query->expr()->eq('s.attendee_id', 'a.id'),
$query->expr()->neq('s.in_call', $query->createNamedParameter(Participant::FLAG_DISCONNECTED, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
)
)
->where($query->expr()->eq('r.token', $query->createNamedParameter($token)))
->andWhere($query->expr()->eq('a.actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)))
->andWhere($query->expr()->eq('a.actor_id', $query->createNamedParameter($userId)));

$result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();

if ($row === false) {
return CallNotificationController::CASE_ROOM_NOT_FOUND;
}

if ($row['active_since'] === null) {
return CallNotificationController::CASE_MISSED_CALL;
}

try {
$activeSince = new \DateTime($row['active_since']);
} catch (\Throwable) {
return CallNotificationController::CASE_MISSED_CALL;
}

if ($row['in_call'] !== null) {
return CallNotificationController::CASE_PARTICIPANT_JOINED;
}

if ($activeSince->getTimestamp() >= $row['last_joined_call']) {
return CallNotificationController::CASE_STILL_CURRENT;
}
return CallNotificationController::CASE_PARTICIPANT_JOINED;
}

/**
* @return Participant[]
*/
Expand Down
174 changes: 174 additions & 0 deletions openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -4560,6 +4560,180 @@
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/call/{token}/notification": {
"get": {
"operationId": "call_notification-state",
"summary": "Check the expected state of a call notification",
"description": "Required capability: `call-notification-state-api`",
"tags": [
"call"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "apiVersion",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"v4"
],
"default": "v4"
}
},
{
"name": "token",
"in": "path",
"description": "Conversation token to check",
"required": true,
"schema": {
"type": "string",
"pattern": "^[a-z0-9]{4,30}$"
}
},
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Notification should be kept alive",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"nullable": true
}
}
}
}
}
}
}
},
"201": {
"description": "Dismiss call notification and show \"Missed call\"-notification instead",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"nullable": true
}
}
}
}
}
}
}
},
"403": {
"description": "Not logged in, try again with auth data sent",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"nullable": true
}
}
}
}
}
}
}
},
"404": {
"description": "Dismiss call notification",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"nullable": true
}
}
}
}
}
}
}
}
}
}
},
"/ocs/v2.php/apps/spreed/api/{apiVersion}/call/{token}/download": {
"get": {
"operationId": "call-download-participants-for-call",
Expand Down
Loading

0 comments on commit 20eb5b6

Please sign in to comment.