Open seletz opened 7 years ago
@seletz Just want to clarify first, with your example I'm thinking you were envisioning something like this?
{
"$jason": {
"head": {
"actions": {
"$load": {
"type": "@JasonetteWebSocketPlugin.connect",
"options": {
"url": "wss://example.com/ws/echo"
}
},
"!WebSocket.message": {
"type": "$set",
"options": {
"messages": "{{var messages = $get.messages; messages.push($jason); return messages;}}"
},
"success": {
"type": "$render"
}
}
}
}
}
}
I think it's best if we walk through using the new notification based action extension code
call
handler to coreFirst, we could add a Jason.call
notification and a notifyCall:
method.
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onForeground) name:UIApplicationDidBecomeActiveNotification object:nil];
self.searchMode = NO;
// Add observers for public API
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notifySuccess:)
name:@"Jason.success"
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notifyError:)
name:@"Jason.error"
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(notifyCall:)
name:@"Jason.call"
object:nil];
}
return self;
}
#pragma mark - Jason Core API Notifications
- (void)notifySuccess:(NSNotification *)notification {
NSDictionary *args = notification.object;
NSLog(@"JasonCore: notifySuccess: %@", args);
[[Jason client] success:args];
}
- (void)notifyError:(NSNotification *)notification {
NSDictionary *args = notification.object;
NSLog(@"JasonCore: notifyError: %@", args);
[[Jason client] error:args];
}
- (void)notifyCall:(NSNotification *)notification {
NSDictionary *args = notification.object;
NSLog(@"JasonCore: notifyCall: %@", args);
[[Jason client] call:args];
}
Then, from the extension side we can add another method (which will be triggered automatically by another method in the extension class, for example a new message from Websocket could call this method) which will then post a Jason.call
notification with a trigger
, along with the payload as options
, like this:
- (void)newMessage:(NSDictionary *)message {
NSDictionary *args = notification.userInfo;
NSDictionary *options = args[@"options"];
NSLog(@"JasonetteDemoAction: text: %@", message);
[[NSNotificationCenter defaultCenter]
postNotificationName:@"Jason.call"
object:self
userInfo:@{
@"trigger": @"!WebSocket.message",
@"options": {@"message": message}
}];
}
This will kick off a new action call chain that looks like this:
{
"trigger": "!WebSocket.message",
"options": {
"message": "..."
}
}
By the way, we already have an event registry tied to the view controller: VC.events, and trigger
is already implemented in a way that it can accept an options
object and pass it along to the resolved action after lookup, so it should work out of the box without further complication.
Lastly, we have a detach: method which takes care of cleanup on viewWillDisappear:
events. So I think we can just place a post notification line in there some where, like this:
[[NSNotificationCenter defaultCenter]
postNotificationName:@"Jason.kill"
object:self
userInfo:nil];
and go back to the extension code and add something like this:
[[NSNotificationCenter defaultCenter]
addObserver: self
selector:@selector(kill:)
name:@"Jason.kill"
object:nil];
...
- (void)kill{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:@"!WebSocket.message"
object:nil];
}
I haven't yet tried this and it just came straight from head so there may be some mistakes, but theoretically I think this should work...so let me know if I'm missing something.
I couldn't actually try it because I was waiting for your pull request haha
Just realized this may work for some cases, but for cases like websockets I think we may need some sort of a "module holder" so that these daemons associated with the actions don't get garbage collected after execution.. Was this what you meant by registry?
Please see PR #62
As for your comments -- I think I need more time to think them through. I'll follow up here.
Was going to sit on this for a bit, but ran into the same architecture problem with the Android side I was working on tonight, so wrote the logic on Android first, and ported it back to iOS for discussion https://github.com/Jasonette/JASONETTE-iOS/pull/63
It covers everything I mentioned above:
call
notification endpoint for JasonBetter explained with an example. We want to do something like this:
{
"$jason": {
"head": {
"actions": {
"$load": {
"type": "$util.alert",
"options": {
"title": "Enter username",
"description": "Enter username to join the chatroom",
"form": [{
"name": "username"
}]
},
"success": {
"type": "@websockets.connect",
"options": {
"url": "ws://chatroom.chatroom/chatroom",
"data": {
"username": "{{$jason.username}}"
}
}
}
},
"!websockets.message": {
"type": "$set",
"options": {
"messages": "{{ var buffer = $get.messages; buffer.push($jason); return buffer }}"
},
"success": {
"type": "$render"
}
},
"!websockets.connected": {
"type": "$util.banner",
"options": {
"title": "Connected",
"description": "Successfully connected to localhost"
}
}
}
}
}
}
First of all, for all this to work, the JasonWebsocketsAction
should stick around after the @websockets.connect
action execution. Currently that's not possible since the module
we use to invoke actions are not designed to stick around. They're currently more like a one-off thing that does its job and goes away. The module itself gets freshly instantiated every time it needs to execute
That's why I've added an NSMutableDictionary type modules
to JasonViewController
, as sort of a modules registry.
Then when there's a new message from the websockets connection, JasonWebsocketsAction
will need to notify Jason core. To do that, we may have a code inside JasonWebsocketsAction
that looks like this:
[[NSNotificationCenter defaultCenter]
postNotificationName:@"Jason.call"
object:self
userInfo:@{
@"trigger": @"!websockets.message",
@"options": {@"message": message}
}];
}
Then naturally it will kick off the action call chain. No need for additional logic. Basically Jason core functions as the action dispatch backbone.
@gliechtenstein WRT the registry -- we could just do away with that by demanding that Event-enabled plug-ins must act as singletons. That way, nothing needs to be changed and plug-ins can stay very easy, on-demand things.
What's not immediately clear to me is how a plug-in would react to events, i.e. how would plug-ins be able to register their own handlers?
Im my original idea it would look something like this (beware, out-of-my head and not even syntax tested):
@implementation FooEventEnabledPlugin
+(id)sharedInstance() { do the magic singleton dance here}
- (id)init {
// if we have a instance, return that one here, else
// create plugin instance
if (self = [super init]) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleAction:)
name:@"FooEventEnabledPlugin.action"
object:nil];
}
return self;
}
-(void)handleAction:(NSNotification *)notification {
NSDictionary *options = [self optionsFromNotification:notification];
if (options) {
// do some work
[someApi doWork:options completion:^(NSDictionary result) {
// got some async result.
// We'll post a new event just in case someone listens.
[[NSNotificationCenter defaultCenter]
postNotificationName:@"!FooEventEnabledPlugin.asyncResult"
object:self
userInfo:@{
@"options": result
}];
}];
//report success
[[NSNotificationCenter defaultCenter]
postNotificationName:@"Jason.ok"
object:self
userInfo:@{
@"options": {@"foo": 42}
}];
}
}
@end
The above plug-in would be used like:
{
...
{
"type": "@FooPluginEventEnabled.action"
"options": { ... }
"success": { ... here we get the 42 result from the action ... }
}
...
{
"type": "!FooPluginEventEnabled.asyncResult"
"options": {}
"success": { here we'd get the result form the async operation }
}
}
It's not clear to me how the above use case would work with your suggestion -- but maybe I'm missing something.
initially I wanted to tie it to the viewcontroller because that way they will automatically go away when the view closes. The reason I was thinking about it this way was because I was trying to make the websockets case work without making too much change to the existing architecture. Basically I was approaching this from a web browser metaphor, where any actions that were running gets reset when a user refreshes or moves away to another page.
But upon thinking about your comment I think I can understand what you're saying. But just to clarify, do we need singletons (vs. tying them under viewcontrollers) because that will enable actions to exist across views? If so, can you give some examples? That would help us get on the same page.
I feel like I am seriously misunderstanding your idea. I think it would help if you can provide full context to your example JSON:
{
...
{
"type": "@FooPluginEventEnabled.action"
"options": { ... }
"success": { ... here we get the 42 result from the action ... }
}
...
{
"type": "!FooPluginEventEnabled.asyncResult"
"options": {}
"success": { here we'd get the result form the async operation }
}
}
More specifically, is this under $jason.head.actions
like usual? or Is this a completely new way of describing actions? I ask because if it's under $jason.head.actions
, these actions should have a name as their keys (which are basically like events, and can be triggered using the trigger
syntax).
Thanks!
Actually, please ping me on slack when you see this, that would be better than going back and forth async
Motivation
So it looks like the plug-in approach #26 will use notifications to communicate with the core. This opens new possibilities for integrations. For example, issue #49 is about web socket support. Such a feature can live in a plug-in w/o problems.
However, web sockets are async in nature, that is, they can receive messages at any time. How would users be able to react on those events?
Note: I use WebSocket as an example here. This is obviously useful for catching other events and could replace the hard-coded "system events" in Jasonette.
Proposal
I propose a new action type for events. Those actions would "register" itself for a notification handler by name:
Details
For the above to work, the hypothetical WebSocket plugin would send a notification on received messages with name
WebSocket.message
.The
!WebSocket.message
action would instruct the core to register a notification handler for theWebSocket.message
notification and do a[[Jason client] success:notification.object]
if the notification is received. This can be nicely done in a block.All this could be done in a plug-in also. But this ia s feature which is core-worthy IMHO 😄
Problems