Start implementing matrix chat integration

Implement auto-login for element-web
This commit is contained in:
Ben 2022-02-26 19:40:21 +01:00
parent 33ca2c93db
commit 940c7bed6e
Signed by: ben
GPG key ID: 0F54A7ED232D3319
9 changed files with 185 additions and 18 deletions

View file

@ -12,5 +12,10 @@ return [
'verb' => 'OPTIONS', 'verb' => 'OPTIONS',
'requirements' => ['path' => '.+'], 'requirements' => ['path' => '.+'],
], ],
[
'name' => 'ticket_api#fetch_chat',
'url' => '/api/v1/tickets/{id}/chat',
'verb' => 'GET',
],
], ],
]; ];

40
extra/element-config.json Normal file
View file

@ -0,0 +1,40 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://localhost:8008",
"server_name": "synapse"
},
"m.identity_server": {
}
},
"disable_custom_urls": false,
"disable_guests": true,
"disable_login_language_selector": false,
"disable_3pid_login": false,
"brand": "Element",
"integrations_ui_url": "",
"integrations_rest_url": "",
"integrations_widgets_urls": [],
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
"uisi_autorageshake_app": "element-auto-uisi",
"defaultCountryCode": "DE",
"showLabsSettings": false,
"features": { },
"default_federate": true,
"default_theme": "light",
"roomDirectory": {
"servers": [
]
},
"piwik": {
},
"enable_presence_by_hs_url": {
},
"settingDefaults": {
"breadcrumbs": true
},
"jitsi": {
"preferredDomain": "meet.element.io"
},
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
}

View file

@ -3,25 +3,48 @@
namespace OCA\UPschooling\Controller; namespace OCA\UPschooling\Controller;
use OCA\UPschooling\AppInfo\Application; use OCA\UPschooling\AppInfo\Application;
use OCA\UPschooling\Db\TicketMapper;
use OCA\UPschooling\Service\MatrixService;
use OCA\UPschooling\Service\TicketService; use OCA\UPschooling\Service\TicketService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
use Psr\Log\LoggerInterface;
use Punic\Data;
class TicketApiController extends ApiController class TicketApiController extends ApiController
{ {
/** @var LoggerInterface */
private $logger;
/** @var TicketService */ /** @var TicketService */
private $service; private $ticketService;
/** @var MatrixService */
private $matrixService;
/** @var TicketMapper */
private $ticketMapper;
/** @var string */ /** @var string */
private $userId; private $userId;
use Errors; use Errors;
public function __construct(IRequest $request, TicketService $service, $userId) public function __construct(
{ IRequest $request,
LoggerInterface $logger,
TicketService $ticketService,
TicketMapper $ticketMapper,
MatrixService $matrixService,
string $userId
) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);
$this->service = $service; $this->logger = $logger;
$this->ticketService = $ticketService;
$this->ticketMapper = $ticketMapper;
$this->matrixService = $matrixService;
$this->userId = $userId; $this->userId = $userId;
} }
@ -30,7 +53,7 @@ class TicketApiController extends ApiController
*/ */
public function index(): DataResponse public function index(): DataResponse
{ {
return new DataResponse($this->service->findAll($this->userId)); return new DataResponse($this->ticketService->findAll($this->userId));
} }
/** /**
@ -39,7 +62,24 @@ class TicketApiController extends ApiController
public function show(int $id): DataResponse public function show(int $id): DataResponse
{ {
return $this->handleNotFound(function () use ($id) { return $this->handleNotFound(function () use ($id) {
return $this->service->find($id, $this->userId); return $this->ticketService->find($id, $this->userId);
});
}
/**
* @NoAdminRequired
*/
public function fetchChat(int $id): DataResponse
{
return $this->handleNotFound(function () use ($id) {
$matrixUser = $this->ticketService->getOrCreateUser($this->userId);
$ticket = $this->ticketMapper->findForUser($id, $matrixUser);
$this->logger->debug("fetchChat found matrix data for room " . $ticket->getMatrixRoom());
return array(
'matrixRoom' => $ticket->getMatrixRoom(), // FIXME: wrong room, create one for helper and user
'matrixAccessToken' => $matrixUser->getMatrixToken(),
'matrixServerUrl' => $this->matrixService->getServerUrl(),
);
}); });
} }
@ -48,7 +88,7 @@ class TicketApiController extends ApiController
*/ */
public function create(string $title, string $content): DataResponse public function create(string $title, string $content): DataResponse
{ {
return new DataResponse($this->service->create($title, $content, $this->userId)); return new DataResponse($this->ticketService->create($title, $content, $this->userId));
} }
/** /**
@ -56,13 +96,13 @@ class TicketApiController extends ApiController
*/ */
public function update(int $id): DataResponse public function update(int $id): DataResponse
{ {
return new DataResponse($this->service->assign($id, $this->userId)); return new DataResponse($this->ticketService->assign($id, $this->userId));
} }
public function destroy(int $id): DataResponse public function destroy(int $id): DataResponse
{ {
return $this->handleNotFound(function () use ($id) { return $this->handleNotFound(function () use ($id) {
return $this->service->delete($id, $this->userId); return $this->ticketService->delete($id, $this->userId);
}); });
} }
} }

View file

@ -10,7 +10,8 @@ class MatrixUser extends Entity implements JsonSerializable {
protected $matrixUser; protected $matrixUser;
protected $matrixToken; protected $matrixToken;
public function jsonSerialize(): array { public function jsonSerialize(): array
{
return [ return [
'id' => $this->id, 'id' => $this->id,
'userId' => $this->userId, 'userId' => $this->userId,

View file

@ -56,7 +56,7 @@ class MatrixService
); );
$this->serverUrl = $this->config->getSystemValueString( $this->serverUrl = $this->config->getSystemValueString(
"upschooling.matrix_server_url", "upschooling.matrix_server_url",
"http://synapse:8008" "http://localhost:8008"
); );
$this->server = $this->config->getSystemValueString( $this->server = $this->config->getSystemValueString(
"upschooling.matrix_server", "upschooling.matrix_server",
@ -216,6 +216,14 @@ class MatrixService
return $roomId; return $roomId;
} }
/**
* @return string the public matrix server url.
*/
public function getServerUrl(): string
{
return $this->serverUrl;
}
private function checkRateLimit() private function checkRateLimit()
{ {
$fullSuperuserId = "@" . $this->superuser . ":" . $this->server; $fullSuperuserId = "@" . $this->superuser . ":" . $this->server;

View file

@ -104,7 +104,7 @@ class TicketService {
} }
/** /**
* matrixTicketContent has all the syncronized data * matrixTicketContent has all the synchronized data
* @param MatrixTicket $ticket the database object. * @param MatrixTicket $ticket the database object.
* @return array a JSON serializable representation of the resolved ticket, for the frontend. * @return array a JSON serializable representation of the resolved ticket, for the frontend.
*/ */
@ -144,7 +144,7 @@ class TicketService {
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
private function getOrCreateUser(string $userId): MatrixUser public function getOrCreateUser(string $userId): MatrixUser
{ {
try { try {
return $this->userMapper->find($userId); return $this->userMapper->find($userId);

1
run.sh
View file

@ -139,6 +139,7 @@ $CONTAINER_RUNTIME run -d \
--name=elementweb \ --name=elementweb \
"--network=container:$($CONTAINER_RUNTIME inspect --format "{{.Id}}" nextcloud)" \ "--network=container:$($CONTAINER_RUNTIME inspect --format "{{.Id}}" nextcloud)" \
-v "$DIR/extra/element-web-nginx.conf:/etc/nginx/conf.d/default.conf" \ -v "$DIR/extra/element-web-nginx.conf:/etc/nginx/conf.d/default.conf" \
-v "$DIR/extra/element-config.json:/app/config.json" \
--hostname elementweb \ --hostname elementweb \
docker.io/vectorim/element-web docker.io/vectorim/element-web

View file

@ -64,7 +64,7 @@ export default {
}).catch(console.error) }).catch(console.error)
axios.get( axios.get(
'api/v1/tickets/1', 'api/v1/tickets/1',
{ headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }, { headers: { Accept: 'application/json' } },
).catch(console.error) ).catch(console.error)
axios.put( axios.put(
'api/v1/tickets/1', 'api/v1/tickets/1',

View file

@ -18,12 +18,18 @@
{{ t('upschooling', 'Speichern') }} {{ t('upschooling', 'Speichern') }}
</button> </button>
<hr> <hr>
<div class="placeholder" /> <iframe id="element-web-frame"
src="about:blank"
title="Embedded Element Web"
width="100%"
height="400" />
</div> </div>
</template> </template>
<script> <script>
import KeyValueTable from './components/KeyValueTable' import KeyValueTable from './components/KeyValueTable'
import axios from '@nextcloud/axios'
export default { export default {
name: 'Ticket', name: 'Ticket',
components: { KeyValueTable }, components: { KeyValueTable },
@ -40,6 +46,20 @@ export default {
description: this.ticket.description, description: this.ticket.description,
} }
}, },
mounted() {
/** @type {HTMLIFrameElement} */
const elementWebFrame = document.getElementById('element-web-frame')
this.loadChat(elementWebFrame).catch((err) => {
console.error('Could not load Element Web in iframe', err)
elementWebFrame.src = 'about:blank'
elementWebFrame.onload = function() {
const textElement = elementWebFrame.contentDocument.createElement('strong')
textElement.innerText = 'Element Web konnte nicht geladen werden.'
elementWebFrame.contentDocument.body.appendChild(textElement)
elementWebFrame.onload = undefined
}
})
},
methods: { methods: {
toLocaleDate(timestamp) { toLocaleDate(timestamp) {
const date = new Date(timestamp) const date = new Date(timestamp)
@ -53,6 +73,58 @@ export default {
back() { back() {
this.$emit('show-ticket-list') this.$emit('show-ticket-list')
}, },
async loadChat(elementWebFrame) {
const matrixInfoResponse = await axios.get(
`api/v1/tickets/${this.ticket.ticketId}/chat`,
{ headers: { Accept: 'application/json' } },
)
if (matrixInfoResponse.status !== 200) {
throw Error(`Received unexpected status code ${matrixInfoResponse.status} for fetching matrix chat data`)
}
if (!matrixInfoResponse.data) {
throw Error('Did not receive any matrix chat data')
}
if (typeof matrixInfoResponse.data !== 'object') {
throw Error('Unexpected return value for fetching matrix chat data')
}
const loginPromise = new Promise(function(resolve, reject) {
const tryLogin = (elementWebFrame, matrixInfo, round) => {
if (elementWebFrame.contentWindow.mxLoginWithAccessToken === undefined) {
console.warn('Couldn\'t login in round ' + round)
setTimeout(() => {
tryLogin(elementWebFrame, matrixInfo, round + 1)
}, 1000)
} else {
elementWebFrame.contentWindow.mxLoginWithAccessToken(
matrixInfo.matrixServerUrl,
matrixInfo.matrixAccessToken,
).then(resolve).catch(reject)
}
}
elementWebFrame.onload = function() {
elementWebFrame.onload = undefined
tryLogin(elementWebFrame, matrixInfoResponse.data, 1)
}
})
// load Element Web
elementWebFrame.src = '/upschooling/element-web/'
loginPromise.then(() => {
console.warn('LOGGED IN')
elementWebFrame.contentWindow.mxDispatcher.dispatch({
action: 'view_home_page',
justRegistered: false,
})
elementWebFrame.contentWindow.mxDispatcher.dispatch({
action: 'view_room',
room_id: matrixInfoResponse.data.matrixRoom,
})
}).catch(console.error)
},
}, },
} }
</script> </script>