Make OS3 notify ready

This commit is contained in:
FinnStutzenstein 2018-10-17 18:04:06 +02:00
parent 57202e74ca
commit 47a09e4bce
7 changed files with 189 additions and 52 deletions

View File

@ -1,10 +1,62 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { OpenSlidesComponent } from 'app/openslides.component'; import { OpenSlidesComponent } from 'app/openslides.component';
import { WebsocketService } from './websocket.service'; 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<T> {
/**
* 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<T> extends NotifyBase<T> {
/**
* 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<T> extends NotifyBase<T> {
/**
* 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' providedIn: 'root'
}) })
export class NotifyService extends OpenSlidesComponent { export class NotifyService extends OpenSlidesComponent {
/**
* A general subject for all messages.
*/
private notifySubject = new Subject<NotifyResponse<any>>();
/**
* Subjects for specific messages.
*/
private messageSubjects: {
[name: string]: Subject<NotifyResponse<any>>;
} = {};
/** /**
* Constructor to create the NotifyService. Registers itself to the WebsocketService. * Constructor to create the NotifyService. Registers itself to the WebsocketService.
* @param websocketService * @param websocketService
*/ */
public constructor(private websocketService: WebsocketService) { public constructor(private websocketService: WebsocketService, private operator: OperatorService) {
super(); super();
websocketService.getOberservable<any>('notify').subscribe(notify => {
this.receive(notify); websocketService.getOberservable<NotifyResponse<any>>('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 { * Sents a notify message to all users (so all clients that are online).
console.log('recv', notify); * @param name The name of the notify message
// TODO: Use a Subject, so one can subscribe and get notifies. * @param content The payload to send
*/
public sendToAllUsers<T>(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 * Sends a notify message to all open clients with the given users logged in.
* @param notify the notify objects * @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<T>(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<T>(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<T>(name: string, content: T, users?: number[] | boolean, channels?: string[]): void {
const notify: NotifyRequest<T> = {
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); this.websocketService.send('notify', notify);
} }
/**
* Returns a general observalbe of all notify messages.
*/
public getObservable(): Observable<NotifyResponse<any>> {
return this.notifySubject.asObservable();
}
/**
* Returns an observable for a specific type of messages.
* @param name The name of all messages to observe.
*/
public getMessageObservable<T>(name: string): Observable<NotifyResponse<T>> {
if (!this.messageSubjects[name]) {
this.messageSubjects[name] = new Subject<NotifyResponse<any>>();
}
return this.messageSubjects[name].asObservable() as Observable<NotifyResponse<T>>;
}
} }

View File

@ -10,8 +10,7 @@ import { DataStoreService } from '../../../../core/services/data-store.service';
@Component({ @Component({
selector: 'os-start', selector: 'os-start',
templateUrl: './start.component.html', templateUrl: './start.component.html'
styleUrls: ['./start.component.css']
}) })
export class StartComponent extends BaseComponent implements OnInit { export class StartComponent extends BaseComponent implements OnInit {
public welcomeTitle: string; public welcomeTitle: string;

View File

@ -17,18 +17,32 @@ class NotifyWebsocketClientMessage(BaseWebsocketClientMessage):
identifier = "notify" identifier = "notify"
schema = { schema = {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "Notify elements.", "title": "Notify element.",
"description": "Elements that one client can send to one or many other clients.", "description": "Element that one client can send to one or many other clients.",
"type": "array", "type": "object",
"items": { "properties": {
"type": "object", "name": {"description": "The name of the notify message", "type": "string"},
"properties": { "content": {"description": "The actual content of this message."},
"projectors": {"type": "array", "items": {"type": "integer"}}, "reply_channels": {
"reply_channels": {"type": "array", "items": {"type": "string"}}, "description": "A list of channels to send this message to.",
"users": {"type": "array", "items": {"type": "integer"}}, "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( async def receive_content(
@ -39,7 +53,7 @@ class NotifyWebsocketClientMessage(BaseWebsocketClientMessage):
{ {
"type": "send_notify", "type": "send_notify",
"incomming": content, "incomming": content,
"senderReplyChannelName": consumer.channel_name, "senderChannelName": consumer.channel_name,
"senderUserId": consumer.scope["user"]["id"], "senderUserId": consumer.scope["user"]["id"],
}, },
) )

View File

@ -70,26 +70,21 @@ class SiteConsumer(ProtocollAsyncJsonWebsocketConsumer):
Send a notify message to the user. Send a notify message to the user.
""" """
user_id = self.scope["user"]["id"] user_id = self.scope["user"]["id"]
item = event["incomming"]
out = [] users = item.get("users")
for item in event["incomming"]: reply_channels = item.get("replyChannels")
users = item.get("users") if (
reply_channels = item.get("replyChannels") (isinstance(users, bool) and users)
if ( or (isinstance(users, list) and user_id in users)
(isinstance(users, list) and user_id in users) or (
or ( isinstance(reply_channels, list) and self.channel_name in reply_channels
isinstance(reply_channels, list) )
and self.channel_name in reply_channels or (users is None and reply_channels is None)
) ):
or users is None item["senderChannelName"] = event["senderChannelName"]
and reply_channels is None item["senderUserId"] = event["senderUserId"]
): await self.send_json(type="notify", content=item)
item["senderReplyChannelName"] = event.get("senderReplyChannelName")
item["senderUserId"] = event.get("senderUserId")
out.append(item)
if out:
await self.send_json(type="notify", content=out)
async def send_data(self, event: Dict[str, Any]) -> None: async def send_data(self, event: Dict[str, Any]) -> None:
""" """

View File

@ -248,18 +248,18 @@ async def test_send_notify(communicator, set_config):
await communicator.send_json_to( await communicator.send_json_to(
{ {
"type": "notify", "type": "notify",
"content": [{"testmessage": "foobar, what else."}], "content": {"content": "foobar, what else.", "name": "message_name"},
"id": "test", "id": "test",
} }
) )
response = await communicator.receive_json_from() response = await communicator.receive_json_from()
content = response["content"] content = response["content"]
assert isinstance(content, list) assert isinstance(content, dict)
assert len(content) == 1 assert content["content"] == "foobar, what else."
assert content[0]["testmessage"] == "foobar, what else." assert content["name"] == "message_name"
assert "senderReplyChannelName" in content[0] assert "senderChannelName" in content
assert content[0]["senderUserId"] == 0 assert content["senderUserId"] == 0
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@ -6,7 +6,11 @@ from openslides.utils.websocket import schema
def test_notify_schema_validation(): def test_notify_schema_validation():
# This raises a validaten error if it fails # 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) jsonschema.validate(message, schema)