Skip to content

Commit

Permalink
Work done on Friday eve
Browse files Browse the repository at this point in the history
  • Loading branch information
robmoffat committed Jun 10, 2024
1 parent 3f0bc09 commit b4de920
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@kite9/web-fdc3",
"private": true,
"version": "0.0.29",
"version": "0.0.30",
"workspaces": [
"packages/*"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@kite9/client",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"version": "0.0.29",
"version": "0.0.30",
"scripts": {
"build": "tsc --module es2022",
"clean": "rimraf dist; rimraf cucumber-report.html; rimraf coverage"
Expand Down
2 changes: 1 addition & 1 deletion packages/da-proxy/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kite9/da-proxy",
"version": "0.0.29",
"version": "0.0.30",
"files": [
"dist"
],
Expand Down
4 changes: 2 additions & 2 deletions packages/da-proxy/src/intents/DefaultIntentSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export class DefaultIntentSupport implements IntentSupport {

private async createResultPromise(messageOut: RaiseIntentAgentRequest): Promise<IntentResult> {
const rp = await this.messaging.waitFor<RaiseIntentResultAgentResponse>(m => (
(m.meta.requestUuid == messageOut.meta.requestUuid) &&
(m.type == 'raiseIntentResultResponse')))
(m.type == 'raiseIntentResultResponse') &&
(m.meta.requestUuid == messageOut.meta.requestUuid)))

if (!rp) {
// probably a timeout
Expand Down
2 changes: 1 addition & 1 deletion packages/da-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kite9/da-server",
"version": "0.0.29",
"version": "0.0.30",
"files": [
"dist"
],
Expand Down
110 changes: 98 additions & 12 deletions packages/da-server/src/handlers/BroadcastHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MessageHandler } from "../BasicFDC3Server";
import { AppMetadata, BroadcastAgentRequest, ConnectionStep2Hello, ConnectionStep3Handshake } from "@finos/fdc3/dist/bridging/BridgingTypes";
import { AppMetadata, BroadcastAgentRequest, ConnectionStep2Hello, ConnectionStep3Handshake, PrivateChannelEventListenerAddedAgentRequest, PrivateChannelEventListenerRemovedAgentRequest, PrivateChannelOnDisconnectAgentRequest } from "@finos/fdc3/dist/bridging/BridgingTypes";
import { ServerContext } from "../ServerContext";
import {
PrivateChannelOnAddContextListenerAgentRequest,
Expand All @@ -16,13 +16,36 @@ type ListenerRegistration = {
contextType: string | null
}

function matches(lr1: ListenerRegistration, lr2: ListenerRegistration): boolean {
function listenerRegistrationMatches(lr1: ListenerRegistration, lr2: ListenerRegistration): boolean {
return (lr1.appId == lr2.appId) &&
(lr1.instanceId == lr2.instanceId) &&
(lr1.channelId == lr2.channelId) &&
(lr1.contextType == lr2.contextType)
}

type ChannelEventType = 'onAddContextListener' | 'onUnsubscribe' | 'onDisconnect'

type ChannelEventListener = {
appId: string,
instanceId: string,
channelId: string,
eventType: ChannelEventType
}

function channelEventListenerMatches(lr1: ChannelEventListener, lr2: ChannelEventListener): boolean {
return (lr1.appId == lr2.appId) &&
(lr1.instanceId == lr2.instanceId) &&
(lr1.channelId == lr2.channelId) &&
(lr1.eventType == lr2.eventType)
}

function channelEventListenerInvoked(cel: ChannelEventListener, lr: ListenerRegistration, eventType: ChannelEventType): boolean {
return (cel.appId == lr.appId) &&
(cel.instanceId == lr.instanceId) &&
(cel.channelId == lr.channelId) &&
(cel.eventType == eventType)
}

function createListenerRegistration(msg:
PrivateChannelOnAddContextListenerAgentRequest |
PrivateChannelOnUnsubscribeAgentRequest): ListenerRegistration {
Expand All @@ -38,11 +61,15 @@ function createListenerRegistration(msg:
type ChannelState = { [channelId: string]: ContextElement[] }
type ChannelType = { [channelId: string]: 'user' | 'app' | 'private' }

type EventMessage = PrivateChannelOnUnsubscribeAgentRequest | OnUnsubscribeAgentRequest | PrivateChannelOnAddContextListenerAgentRequest | OnAddContextListenerAgentRequest

export class BroadcastHandler implements MessageHandler {

private regs: ListenerRegistration[] = []
private state: ChannelState = {}
private type: ChannelType = {}
private readonly contextListeners: ListenerRegistration[] = []
private readonly eventListeners: ChannelEventListener[] = []
private readonly state: ChannelState = {}
private readonly type: ChannelType = {}

private readonly desktopAgentName: string

constructor(name: string) {
Expand All @@ -58,6 +85,9 @@ export class BroadcastHandler implements MessageHandler {
case 'PrivateChannel.broadcast': return this.handleBroadcast(msg as PrivateChannelBroadcastAgentRequest, sc)
case 'PrivateChannel.onAddContextListener': return this.handleOnAddContextListener(msg as PrivateChannelOnAddContextListenerAgentRequest, sc)
case 'PrivateChannel.onUnsubscribe': return this.handleOnUnsubscribe(msg as PrivateChannelOnUnsubscribeAgentRequest, sc)
case 'PrivateChannel.onDisconnect': return this.handleOnDisconnect(msg as PrivateChannelOnDisconnectAgentRequest, from, sc)
case 'PrivateChannel.eventListenerAdded': return this.handleEventListenerAdded(msg as PrivateChannelEventListenerAddedAgentRequest, from)
case 'PrivateChannel.eventListenerRemoved': return this.handleEventListenerRemoved(msg as PrivateChannelEventListenerRemovedAgentRequest, from)

// although we don't have messages for these yet, we're going to need them. See: https://github.com/finos/FDC3/issues/1171
case 'onUnsubscribe': return this.handleOnUnsubscribe(msg as OnUnsubscribeAgentRequest, sc)
Expand All @@ -69,6 +99,29 @@ export class BroadcastHandler implements MessageHandler {
}
}

createChannelEventListener(arg0: PrivateChannelEventListenerRemovedAgentRequest | PrivateChannelEventListenerAddedAgentRequest, from: AppMetadata): ChannelEventListener {
const el: ChannelEventListener = {
appId: from.appId,
instanceId: from.instanceId!!,
channelId: arg0.payload.channelId,
eventType: arg0.payload.listenerType
}
return el
}

handleEventListenerRemoved(arg0: PrivateChannelEventListenerRemovedAgentRequest, from: AppMetadata) {
const toRemove = this.createChannelEventListener(arg0, from)
const fi = this.eventListeners.findIndex(e => channelEventListenerMatches(e, toRemove))
if (fi > -1) {
this.eventListeners.splice(fi, 1)
}
}

handleEventListenerAdded(arg0: PrivateChannelEventListenerAddedAgentRequest, from: AppMetadata) {
const el = this.createChannelEventListener(arg0, from)
this.eventListeners.push(el)
}

handleHello(_hello: ConnectionStep2Hello, sc: ServerContext, from: AppMetadata) {
const out: ConnectionStep3Handshake = {
type: 'handshake',
Expand Down Expand Up @@ -96,24 +149,57 @@ export class BroadcastHandler implements MessageHandler {

}

handleOnUnsubscribe(arg0: PrivateChannelOnUnsubscribeAgentRequest | OnUnsubscribeAgentRequest, _sc: ServerContext) {
const lr = createListenerRegistration(arg0)
const fi = this.regs.findIndex((e) => matches(e, lr))
invokeEventListeners(msg: EventMessage, lr: ListenerRegistration, eventType: ChannelEventType, sc: ServerContext) {
this.eventListeners
.filter(e => channelEventListenerInvoked(e, lr, eventType))
.forEach(e => sc.post(msg, { appId: e.appId, instanceId: e.instanceId }))
}

unsubscribe(lr: ListenerRegistration, sc: ServerContext, type: 'onUnsubscribe' | 'PrivateChannel.onUnsubscribe') {
const fi = this.contextListeners.findIndex((e) => listenerRegistrationMatches(e, lr))
if (fi > -1) {
this.regs.splice(fi, 1)
this.contextListeners.splice(fi, 1)
}

this.invokeEventListeners({
type,
meta: {
requestUuid: sc.createUUID(),
timestamp: new Date(),
},
payload: {
channelId: lr.channelId,
contextType: lr.contextType
}

} as EventMessage, lr, 'onUnsubscribe', sc)
}

handleOnUnsubscribe(arg0: PrivateChannelOnUnsubscribeAgentRequest | OnUnsubscribeAgentRequest, sc: ServerContext) {
const lr = createListenerRegistration(arg0)
this.unsubscribe(lr, sc, arg0.type)
}

handleOnDisconnect(arg0: PrivateChannelOnDisconnectAgentRequest, from: AppMetadata, sc: ServerContext) {
// first, unsubscribe all listeners from this app to the channel
const toUnsubscribe = this.contextListeners.filter(r => (r.appId == from.appId) && (r.instanceId == from.instanceId) && (r.channelId == arg0.payload.channelId))
toUnsubscribe.forEach(u => this.unsubscribe(u, sc, 'PrivateChannel.onUnsubscribe'))

// now, fire the disconnect event listeners
//this.eventListeners.filter(cel => (cel.appId == from.appId))
}

handleOnAddContextListener(arg0: PrivateChannelOnAddContextListenerAgentRequest | OnAddContextListenerAgentRequest, _sc: ServerContext) {
handleOnAddContextListener(arg0: PrivateChannelOnAddContextListenerAgentRequest | OnAddContextListenerAgentRequest, sc: ServerContext) {
const lr = createListenerRegistration(arg0)
this.regs.push(lr)
this.contextListeners.push(lr)
this.invokeEventListeners(arg0, lr, 'onAddContextListener', sc)
}

async handleBroadcast(arg0: PrivateChannelBroadcastAgentRequest | BroadcastAgentRequest, sc: ServerContext) {
const channelId = arg0.payload.channelId
const context = arg0.payload.context
const contextType = context.type
const lr = this.regs
const lr = this.contextListeners
const privateChannel = arg0.type == "PrivateChannel.broadcast"

function getPrivateChannelRecipients(): AppMetadata[] {
Expand Down
49 changes: 32 additions & 17 deletions packages/da-server/test/features/private-channel.feature
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
Feature: Relaying Private Channel Broadcast messages

Background:
Background:
Given A newly instantiated FDC3 Server
# Scenario: Broadcast message to no-one
# When "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
# Then messaging will have outgoing posts
# | msg.source.AppId |
# And messaging will have 0 posts
# Scenario: Broadcast message sent to one listener
# When "App2/a2" adds a context listener on private channel "channel1" with type "fdc3.instrument"
# And "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
# Then messaging will have outgoing posts
# | msg.meta.source.appId | msg.meta.source.instanceId | msg.payload.context.type | msg.meta.destination.appId | msg.meta.destination.instanceId |
# | App1 | a1 | fdc3.instrument | App2 | a2 |
# Scenario: Broadcast message sent but listener has unsubscribed
# When "App2/a2" adds a context listener on private channel "channel1" with type "fdc3.instrument"
# And "App2/a2" removes a context listener on private channel "channel1" with type "fdc3.instrument"
# And "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
# Then messaging will have outgoing posts
# | msg.source.AppId | msg.source.instanceId | msg.payload.context.type |

Scenario: Broadcast message to no-one
When "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
Scenario: Event Listener created for addContextListener
When "App2/a2" adds an AddContextListener on private channel "channel1"
And "App2/a2" adds an onUnsubscribeListener on private channel "channel1"
And "App2/a1" adds a context listener on private channel "channel1" with type "fdc3.instrument"
Then messaging will have outgoing posts
| msg.source.AppId |
And messaging will have 0 posts

Scenario: Broadcast message sent to one listener
When "App2/a2" adds a context listener on private channel "channel1" with type "fdc3.instrument"
And "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
Then messaging will have outgoing posts
| msg.meta.source.appId | msg.meta.source.instanceId | msg.payload.context.type | msg.meta.destination.appId | msg.meta.destination.instanceId |
| App1 | a1 | fdc3.instrument | App2 | a2 |

Scenario: Broadcast message sent but listener has unsubscribed
When "App2/a2" adds a context listener on private channel "channel1" with type "fdc3.instrument"
And "App2/a2" removes context listener on private channel "channel1" with type "fdc3.instrument"
And "App1/a1" broadcasts "fdc3.instrument" on private channel "channel1"
| msg.source.AppId | msg.source.instanceId | msg.payload.context.type |
| blah | bob | squirtle |
And "App2/a1" removes a context listener on private channel "channel1" with type "fdc3.instrument"
Then messaging will have outgoing posts
| msg.source.AppId | msg.source.instanceId | msg.payload.context.type |
| blah | bob | squirtle |
# Scenario: Event Listener removed for addContextListener
# When "App2/a2" adds an AddContextListener on private channel "channel1"
# And "App2/a2" removes an AddContextListener on private channel "channel1"
# And "App2/a1" adds a context listener on private channel "channel1" with type "fdc3.instrument"
# Then messaging will have outgoing posts
# | msg.source.AppId | msg.source.instanceId | msg.payload.context.type |
60 changes: 58 additions & 2 deletions packages/da-server/test/step-definitions/private-channel.steps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { When } from '@cucumber/cucumber'
import { CustomWorld } from '../world';
import { PrivateChannelOnAddContextListenerAgentRequest, PrivateChannelOnUnsubscribeAgentRequest, PrivateChannelBroadcastAgentRequest } from "@finos/fdc3/dist/bridging/BridgingTypes";
import { PrivateChannelOnAddContextListenerAgentRequest, PrivateChannelOnUnsubscribeAgentRequest, PrivateChannelBroadcastAgentRequest, PrivateChannelEventListenerAddedAgentRequest, PrivateChannelEventListenerRemovedAgentRequest } from "@finos/fdc3/dist/bridging/BridgingTypes";
import { contextMap, createMeta } from './generic.steps';


Expand All @@ -18,7 +18,63 @@ When('{string} adds a context listener on private channel {string} with type {st
this.server.receive(message, meta.source)
})

When('{string} removes context listener on private channel {string} with type {string}', function (this: CustomWorld, app: string, channelId: string, contextType: string) {
When('{string} adds an AddContextListener on private channel {string}', function (this: CustomWorld, app: string, channelId: string) {
const meta = createMeta(this, app)
const message = {
meta,
payload: {
channelId,
listenerType: 'onAddContextListener'
},
type: 'PrivateChannel.eventListenerAdded'
} as PrivateChannelEventListenerAddedAgentRequest

this.server.receive(message, meta.source)
})

When('{string} removes an AddContextListener on private channel {string}', function (this: CustomWorld, app: string, channelId: string) {
const meta = createMeta(this, app)
const message = {
meta,
payload: {
channelId,
listenerType: 'onAddContextListener'
},
type: 'PrivateChannel.eventListenerRemoved'
} as PrivateChannelEventListenerRemovedAgentRequest

this.server.receive(message, meta.source)
})

When('{string} adds an onUnsubscribeListener on private channel {string}', function (this: CustomWorld, app: string, channelId: string) {
const meta = createMeta(this, app)
const message = {
meta,
payload: {
channelId,
listenerType: 'onUnsubscribe'
},
type: 'PrivateChannel.eventListenerAdded'
} as PrivateChannelEventListenerAddedAgentRequest

this.server.receive(message, meta.source)
})

When('{string} removes an onUnsubscribeListener on private channel {string}', function (this: CustomWorld, app: string, channelId: string) {
const meta = createMeta(this, app)
const message = {
meta,
payload: {
channelId,
listenerType: 'onUnsubscribe'
},
type: 'PrivateChannel.eventListenerRemoved'
} as PrivateChannelEventListenerRemovedAgentRequest

this.server.receive(message, meta.source)
})

When('{string} removes a context listener on private channel {string} with type {string}', function (this: CustomWorld, app: string, channelId: string, contextType: string) {
const meta = createMeta(this, app)
const message = {
meta,
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@kite9/demo",
"private": true,
"version": "0.0.29",
"version": "0.0.30",
"scripts": {
"dev": "nodemon -w src/server -x tsx src/server/main.ts",
"start": "NODE_ENV=production tsx src/server/main.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/fdc3-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kite9/fdc3-common",
"version": "0.0.29",
"version": "0.0.30",
"files": [
"dist"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/fdc3-workbench/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fdc3-workbench",
"version": "0.0.29",
"version": "0.0.30",
"private": true,
"homepage": ".",
"license": "Apache-2.0",
Expand Down

0 comments on commit b4de920

Please sign in to comment.