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 { 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<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'
})
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.
* @param websocketService
*/
public constructor(private websocketService: WebsocketService) {
public constructor(private websocketService: WebsocketService, private operator: OperatorService) {
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 {
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<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
* @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<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);
}
/**
* 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({
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;

View File

@ -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"],
},
)

View File

@ -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:
"""

View File

@ -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

View File

@ -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)