Make OS3 notify ready
This commit is contained in:
parent
57202e74ca
commit
47a09e4bce
@ -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>>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"projectors": {"type": "array", "items": {"type": "integer"}},
|
"name": {"description": "The name of the notify message", "type": "string"},
|
||||||
"reply_channels": {"type": "array", "items": {"type": "string"}},
|
"content": {"description": "The actual content of this message."},
|
||||||
"users": {"type": "array", "items": {"type": "integer"}},
|
"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(
|
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"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -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 = []
|
|
||||||
for item in event["incomming"]:
|
|
||||||
users = item.get("users")
|
users = item.get("users")
|
||||||
reply_channels = item.get("replyChannels")
|
reply_channels = item.get("replyChannels")
|
||||||
if (
|
if (
|
||||||
(isinstance(users, list) and user_id in users)
|
(isinstance(users, bool) and users)
|
||||||
|
or (isinstance(users, list) and user_id in users)
|
||||||
or (
|
or (
|
||||||
isinstance(reply_channels, list)
|
isinstance(reply_channels, list) and self.channel_name in reply_channels
|
||||||
and self.channel_name in reply_channels
|
|
||||||
)
|
)
|
||||||
or users is None
|
or (users is None and reply_channels is None)
|
||||||
and reply_channels is None
|
|
||||||
):
|
):
|
||||||
item["senderReplyChannelName"] = event.get("senderReplyChannelName")
|
item["senderChannelName"] = event["senderChannelName"]
|
||||||
item["senderUserId"] = event.get("senderUserId")
|
item["senderUserId"] = event["senderUserId"]
|
||||||
out.append(item)
|
await self.send_json(type="notify", content=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:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user