From 47a09e4bce48ef2cf0a193b474f11e9ba7ecc16f Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Wed, 17 Oct 2018 18:04:06 +0200 Subject: [PATCH] Make OS3 notify ready --- .../src/app/core/services/notify.service.ts | 151 ++++++++++++++++-- .../components/start/start.component.css | 0 .../components/start/start.component.ts | 3 +- openslides/core/websocket.py | 36 +++-- openslides/utils/consumers.py | 33 ++-- tests/integration/utils/test_consumers.py | 12 +- tests/unit/core/test_websocket.py | 6 +- 7 files changed, 189 insertions(+), 52 deletions(-) delete mode 100644 client/src/app/site/common/components/start/start.component.css diff --git a/client/src/app/core/services/notify.service.ts b/client/src/app/core/services/notify.service.ts index d3d715aa1..40575d8a2 100644 --- a/client/src/app/core/services/notify.service.ts +++ b/client/src/app/core/services/notify.service.ts @@ -1,10 +1,62 @@ import { Injectable } from '@angular/core'; +import { Subject, Observable } from 'rxjs'; import { OpenSlidesComponent } from 'app/openslides.component'; import { WebsocketService } from './websocket.service'; +import { OperatorService } from './operator.service'; -interface NotifyFormat { - id: number; // Dummy +/** + * Encapslates the name and content of every message regardless of being a request or response. + */ +interface NotifyBase { + /** + * The name of the notify message. + */ + name: string; + + /** + * The content to send. + */ + content: T; +} + +/** + * This interface has all fields for a notify request to the server. Next to name and content + * one can give an array of user ids (or the value `true` for all users) and an array of + * channel names. + */ +export interface NotifyRequest extends NotifyBase { + /** + * User ids (or `true` for all users) to send this message to. + */ + users?: number[] | boolean; + + /** + * An array of channels to send this message to. + */ + replyChannels?: string[]; +} + +/** + * This is the notify-format one recieves from the server. + */ +export interface NotifyResponse extends NotifyBase { + /** + * This is the channel name of the one, who sends this message. Can be use to directly + * answer this message. + */ + senderChannelName: string; + + /** + * The user id of the user who sends this message. It is 0 for Anonymous. + */ + senderUserId: number; + + /** + * This is validated here and is true, if the senderUserId matches the current operator's id. + * It's also true, if one recieves a request from an anonymous and the operator itself is the anonymous. + */ + sendByThisUser: boolean; } /** @@ -14,29 +66,102 @@ interface NotifyFormat { providedIn: 'root' }) export class NotifyService extends OpenSlidesComponent { + /** + * A general subject for all messages. + */ + private notifySubject = new Subject>(); + + /** + * Subjects for specific messages. + */ + private messageSubjects: { + [name: string]: Subject>; + } = {}; + /** * Constructor to create the NotifyService. Registers itself to the WebsocketService. * @param websocketService */ - public constructor(private websocketService: WebsocketService) { + public constructor(private websocketService: WebsocketService, private operator: OperatorService) { super(); - websocketService.getOberservable('notify').subscribe(notify => { - this.receive(notify); + + websocketService.getOberservable>('notify').subscribe(notify => { + notify.sendByThisUser = notify.senderUserId === (this.operator.user ? this.operator.user.id : 0); + this.notifySubject.next(notify); + if (this.messageSubjects[notify.name]) { + this.messageSubjects[notify.name].next(notify); + } }); } - // TODO: Implement this - private receive(notify: NotifyFormat): void { - console.log('recv', notify); - // TODO: Use a Subject, so one can subscribe and get notifies. + /** + * Sents a notify message to all users (so all clients that are online). + * @param name The name of the notify message + * @param content The payload to send + */ + public sendToAllUsers(name: string, content: T): void { + this.send(name, content); } - // TODO: Make this api better: e.g. send(data, users?, projectors?, channel?, ...) /** - * Sents a notify object to the server - * @param notify the notify objects + * Sends a notify message to all open clients with the given users logged in. + * @param name The name of th enotify message + * @param content The payload to send. + * @param users Multiple user ids. */ - public send(notify: NotifyFormat): void { + public sendToUsers(name: string, content: T, ...users: number[]): void { + this.send(name, content, users); + } + + /** + * Sends a notify message to all given channels. + * @param name The name of th enotify message + * @param content The payload to send. + * @param channels Multiple channels to send this message to. + */ + public sendToChannels(name: string, content: T, ...channles: string[]): void { + this.send(name, content, null, channles); + } + + /** + * General send function for notify messages. + * @param name The name of the notify message + * @param content The payload to send. + * @param users Either an array of IDs or `true` meaning of sending this message to all online users clients. + * @param channels An array of channels to send this message to. + */ + public send(name: string, content: T, users?: number[] | boolean, channels?: string[]): void { + const notify: NotifyRequest = { + name: name, + content: content + }; + if (typeof users === 'boolean' && users !== true) { + throw new Error('You just can give true as a boolean to send this message to all users.'); + } + if (users !== null) { + notify.users = users; + } + if (channels !== null) { + notify.replyChannels = channels; + } this.websocketService.send('notify', notify); } + + /** + * Returns a general observalbe of all notify messages. + */ + public getObservable(): Observable> { + return this.notifySubject.asObservable(); + } + + /** + * Returns an observable for a specific type of messages. + * @param name The name of all messages to observe. + */ + public getMessageObservable(name: string): Observable> { + if (!this.messageSubjects[name]) { + this.messageSubjects[name] = new Subject>(); + } + return this.messageSubjects[name].asObservable() as Observable>; + } } diff --git a/client/src/app/site/common/components/start/start.component.css b/client/src/app/site/common/components/start/start.component.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/app/site/common/components/start/start.component.ts b/client/src/app/site/common/components/start/start.component.ts index 5db5b6e65..72ebbe28b 100644 --- a/client/src/app/site/common/components/start/start.component.ts +++ b/client/src/app/site/common/components/start/start.component.ts @@ -10,8 +10,7 @@ import { DataStoreService } from '../../../../core/services/data-store.service'; @Component({ selector: 'os-start', - templateUrl: './start.component.html', - styleUrls: ['./start.component.css'] + templateUrl: './start.component.html' }) export class StartComponent extends BaseComponent implements OnInit { public welcomeTitle: string; diff --git a/openslides/core/websocket.py b/openslides/core/websocket.py index d779eb579..873e24a36 100644 --- a/openslides/core/websocket.py +++ b/openslides/core/websocket.py @@ -17,18 +17,32 @@ class NotifyWebsocketClientMessage(BaseWebsocketClientMessage): identifier = "notify" schema = { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Notify elements.", - "description": "Elements that one client can send to one or many other clients.", - "type": "array", - "items": { - "type": "object", - "properties": { - "projectors": {"type": "array", "items": {"type": "integer"}}, - "reply_channels": {"type": "array", "items": {"type": "string"}}, - "users": {"type": "array", "items": {"type": "integer"}}, + "title": "Notify element.", + "description": "Element that one client can send to one or many other clients.", + "type": "object", + "properties": { + "name": {"description": "The name of the notify message", "type": "string"}, + "content": {"description": "The actual content of this message."}, + "reply_channels": { + "description": "A list of channels to send this message to.", + "type": "array", + "items": {"type": "string"}, + }, + "users": { + "anyOf": [ + { + "description": "A list of user ids to send this message to.", + "type": "array", + "items": {"type": "integer"}, + }, + { + "description": "This flag indicates, that this message should be send to all users.", + "enum": [True], + }, + ] }, }, - "minItems": 1, + "required": ["name", "content"], } async def receive_content( @@ -39,7 +53,7 @@ class NotifyWebsocketClientMessage(BaseWebsocketClientMessage): { "type": "send_notify", "incomming": content, - "senderReplyChannelName": consumer.channel_name, + "senderChannelName": consumer.channel_name, "senderUserId": consumer.scope["user"]["id"], }, ) diff --git a/openslides/utils/consumers.py b/openslides/utils/consumers.py index f0366ec6d..21d8795bd 100644 --- a/openslides/utils/consumers.py +++ b/openslides/utils/consumers.py @@ -70,26 +70,21 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer): Send a notify message to the user. """ user_id = self.scope["user"]["id"] + item = event["incomming"] - out = [] - for item in event["incomming"]: - users = item.get("users") - reply_channels = item.get("replyChannels") - if ( - (isinstance(users, list) and user_id in users) - or ( - isinstance(reply_channels, list) - and self.channel_name in reply_channels - ) - or users is None - and reply_channels is None - ): - item["senderReplyChannelName"] = event.get("senderReplyChannelName") - item["senderUserId"] = event.get("senderUserId") - out.append(item) - - if out: - await self.send_json(type="notify", content=out) + users = item.get("users") + reply_channels = item.get("replyChannels") + if ( + (isinstance(users, bool) and users) + or (isinstance(users, list) and user_id in users) + or ( + isinstance(reply_channels, list) and self.channel_name in reply_channels + ) + or (users is None and reply_channels is None) + ): + item["senderChannelName"] = event["senderChannelName"] + item["senderUserId"] = event["senderUserId"] + await self.send_json(type="notify", content=item) async def send_data(self, event: Dict[str, Any]) -> None: """ diff --git a/tests/integration/utils/test_consumers.py b/tests/integration/utils/test_consumers.py index 5b43d1c60..e364a3838 100644 --- a/tests/integration/utils/test_consumers.py +++ b/tests/integration/utils/test_consumers.py @@ -248,18 +248,18 @@ async def test_send_notify(communicator, set_config): await communicator.send_json_to( { "type": "notify", - "content": [{"testmessage": "foobar, what else."}], + "content": {"content": "foobar, what else.", "name": "message_name"}, "id": "test", } ) response = await communicator.receive_json_from() content = response["content"] - assert isinstance(content, list) - assert len(content) == 1 - assert content[0]["testmessage"] == "foobar, what else." - assert "senderReplyChannelName" in content[0] - assert content[0]["senderUserId"] == 0 + assert isinstance(content, dict) + assert content["content"] == "foobar, what else." + assert content["name"] == "message_name" + assert "senderChannelName" in content + assert content["senderUserId"] == 0 @pytest.mark.asyncio diff --git a/tests/unit/core/test_websocket.py b/tests/unit/core/test_websocket.py index 7ad28e25d..cdc5279dc 100644 --- a/tests/unit/core/test_websocket.py +++ b/tests/unit/core/test_websocket.py @@ -6,7 +6,11 @@ from openslides.utils.websocket import schema def test_notify_schema_validation(): # This raises a validaten error if it fails - message = {"id": "test-message", "type": "notify", "content": [{"users": [5]}]} + message = { + "id": "test-message", + "type": "notify", + "content": {"name": "testname", "content": ["some content"]}, + } jsonschema.validate(message, schema)