-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #65 from nkolba/fdc3-handlers
initial commit for fdc3 handlers refactor
- Loading branch information
Showing
30 changed files
with
2,104 additions
and
2,499 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { getRuntime } from '/@/index'; | ||
import { RuntimeMessage } from '/@/handlers/runtimeMessage'; | ||
import { View } from '/@/view'; | ||
import { FDC3Listener } from '/@/types/FDC3Listener'; | ||
import { FDC3_TOPICS } from './topics'; | ||
|
||
interface ViewListener { | ||
view: View; | ||
listenerId: string; | ||
} | ||
|
||
export const broadcast = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
const contexts = runtime.getContexts(); | ||
const source = message.source ? runtime.getView(message.source) : null; | ||
|
||
//if there is an instanceId provided on the message - this is the instance target of the broadcast | ||
//meaning this is a point-to-point com between two instances | ||
//if the target listener is registered for the source instance, then dispatch the context | ||
//else, add to the pending queue for instances | ||
const targetId: string | undefined = | ||
(message.data && message.data.instanceId) || undefined; | ||
if (targetId) { | ||
console.log( | ||
`broadcast message = '${JSON.stringify( | ||
message, | ||
)}' target = '${targetId}' source = '${message.source}'`, | ||
); | ||
let setPending = false; | ||
const target = runtime.getView(targetId); | ||
const viewListeners: Array<ViewListener> = []; | ||
if (target) { | ||
target.listeners.forEach((l: FDC3Listener) => { | ||
if (!l.intent) { | ||
if ( | ||
!l.contextType || | ||
(l.contextType && | ||
l.contextType === message.data && | ||
message.data.context && | ||
message.data.context.type) | ||
) { | ||
viewListeners.push({ | ||
view: target, | ||
listenerId: l.listenerId, | ||
}); | ||
} | ||
} | ||
}); | ||
if (viewListeners.length > 0) { | ||
viewListeners.forEach((viewL: ViewListener) => { | ||
const data = { | ||
listenerId: viewL.listenerId, | ||
eventId: message.data && message.data.eventId, | ||
ts: message.data && message.data.ts, | ||
context: message.data && message.data.context, | ||
}; | ||
viewL.view.content.webContents.send(FDC3_TOPICS.CONTEXT, { | ||
topic: 'context', | ||
listenerId: viewL.listenerId, | ||
data: data, | ||
source: message.source, | ||
}); | ||
}); | ||
} else { | ||
setPending = true; | ||
} | ||
} | ||
const pendingContext = message.data && message.data.context; | ||
if (setPending && pendingContext && target) { | ||
target.setPendingContext(pendingContext); | ||
} | ||
//if we have a target, we aren't going to go to other channnels - so resolve | ||
return; | ||
} | ||
|
||
//use channel on message first - if one is specified | ||
const channel = | ||
(message.data && message.data.channel) || | ||
(source && source.channel) || | ||
'default'; | ||
|
||
if (channel !== 'default') { | ||
//is the app on a channel? | ||
// update the channel state | ||
const channelContext = contexts.get(channel); | ||
const context = message.data && message.data.context; | ||
if (channelContext && context) { | ||
channelContext.unshift(context); | ||
} | ||
|
||
//if there is a channel, filter on channel | ||
//to filter on channel, check the listener channel andthe view channel (its channel member) | ||
//loop through all views | ||
runtime.getViews().forEach((v: View) => { | ||
//for each view, aggregate applicable listener ids | ||
//listener must match on channel and context type | ||
const viewListeners: Array<string> = []; | ||
v.listeners.forEach((l: FDC3Listener) => { | ||
console.log('viewListener (1st pass)', l); | ||
const matchChannel = | ||
l.channel && l.channel !== 'default' | ||
? l.channel | ||
: v.channel | ||
? v.channel | ||
: 'default'; | ||
if (matchChannel === channel) { | ||
console.log( | ||
'broadcast - matched channel, contextType ', | ||
l.contextType, | ||
); | ||
const contextType = | ||
message.data && message.data.context && message.data.context.type; | ||
if (l.contextType && contextType) { | ||
console.log('contextType match', l.contextType === contextType); | ||
if ( | ||
l.contextType === contextType && | ||
viewListeners.indexOf(l.listenerId) === -1 | ||
) { | ||
viewListeners.push(l.listenerId); | ||
} | ||
} else if (viewListeners.indexOf(l.listenerId) === -1) { | ||
console.log('push listener ', l.listenerId); | ||
viewListeners.push(l.listenerId); | ||
} | ||
} | ||
}); | ||
//if there are listeners found, broadcast the context to the view (with all listenerIds) | ||
if (viewListeners.length > 0) { | ||
v.content.webContents.send(FDC3_TOPICS.CONTEXT, { | ||
topic: 'context', | ||
listenerIds: viewListeners, | ||
data: { | ||
eventId: message.data && message.data.eventId, | ||
ts: message.data && message.data.ts, | ||
context: message.data && message.data.context, | ||
}, | ||
source: message.source, | ||
}); | ||
} | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { getRuntime } from '/@/index'; | ||
import { RuntimeMessage } from '/@/handlers/runtimeMessage'; | ||
import { ChannelData } from '/@/types/FDC3Data'; | ||
import { Context, ChannelError } from '@finos/fdc3'; | ||
import { systemChannels } from './systemChannels'; | ||
|
||
export const getSystemChannels = async () => { | ||
return systemChannels; | ||
}; | ||
|
||
export const getCurrentChannel = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
|
||
const view = runtime.getView(message.source); | ||
return view?.channel ? getChannelMeta(view.channel) : null; | ||
}; | ||
|
||
export const getCurrentContext = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
|
||
const channel = (message.data && message.data.channel) || undefined; | ||
const type = (message.data && message.data.contextType) || undefined; | ||
let ctx: Context | null = null; | ||
if (channel) { | ||
const contexts = runtime.getContexts(); | ||
const channelContext = contexts.get(channel); | ||
if (type) { | ||
if (channelContext) { | ||
ctx = | ||
channelContext.find((c) => { | ||
return c.type === type; | ||
}) || null; | ||
} | ||
} else { | ||
ctx = channelContext && channelContext[0] ? channelContext[0] : ctx; | ||
} | ||
} | ||
return ctx; | ||
}; | ||
|
||
export const getOrCreateChannel = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
const id = (message.data && message.data.channelId) || 'default'; | ||
//reject with error is reserved 'default' term | ||
if (id === 'default') { | ||
throw ChannelError.CreationFailed; | ||
} else { | ||
let channel: ChannelData | null = getChannelMeta(id); | ||
|
||
//if not found... create as an app channel | ||
if (!channel) { | ||
channel = { id: id, type: 'app' }; | ||
//add an entry for the context listeners | ||
//contextListeners.set(id, new Map()); | ||
runtime.getContexts().set(id, []); | ||
runtime.setAppChannel(channel); | ||
} | ||
if (channel) { | ||
return channel; | ||
} else { | ||
return; | ||
} | ||
} | ||
}; | ||
|
||
export const leaveCurrentChannel = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
//'default' means we have left all channels | ||
const view = runtime.getView(message.source); | ||
if (view) { | ||
view.parent?.joinViewToChannel('default', view); | ||
return; | ||
} else { | ||
throw 'View not found'; | ||
} | ||
}; | ||
|
||
export const joinChannel = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
const channel = message.data && message.data.channel; | ||
const view = runtime.getView(message.source); | ||
if (channel && view) { | ||
await view.parent?.joinViewToChannel( | ||
channel, | ||
view, | ||
(message.data && message.data.restoreOnly) || undefined, | ||
); | ||
return true; | ||
} | ||
}; | ||
|
||
//generate / get full channel object from an id - returns null if channel id is not a system channel or a registered app channel | ||
const getChannelMeta = (id: string): ChannelData | null => { | ||
let channel: ChannelData | null = null; | ||
//is it a system channel? | ||
const sChannels: Array<ChannelData> = systemChannels; | ||
const sc = sChannels.find((c) => { | ||
return c.id === id; | ||
}); | ||
|
||
if (sc) { | ||
channel = { id: id, type: 'system', displayMetadata: sc.displayMetadata }; | ||
} | ||
//is it already an app channel? | ||
if (!channel) { | ||
const runtime = getRuntime(); | ||
const ac = runtime.getAppChannels().find((c) => { | ||
return c.id === id; | ||
}); | ||
if (ac) { | ||
channel = { id: id, type: 'app' }; | ||
} | ||
} | ||
return channel; | ||
}; |
110 changes: 110 additions & 0 deletions
110
packages/main/src/handlers/fdc3/1.2/contextListeners.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { getRuntime } from '/@/index'; | ||
import { RuntimeMessage } from '/@/handlers/runtimeMessage'; | ||
import { View } from '/@/view'; | ||
import { FDC3_TOPICS } from './topics'; | ||
import { Pending } from '/@/types/Pending'; | ||
|
||
export const dropContextListener = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
const id = message.data && message.data?.id; | ||
const view = runtime.getView(message.source); | ||
if (view && id) { | ||
view.listeners = view.listeners.filter((l) => { | ||
return l.listenerId !== id; | ||
}); | ||
} | ||
}; | ||
|
||
export const addContextListener = async (message: RuntimeMessage) => { | ||
const runtime = getRuntime(); | ||
const source = message.source; //this is the app instance calling addContextListener | ||
|
||
//if there is an instanceId specified, this call is to listen to context from a specific app instance | ||
const view = runtime.getView(message.source); | ||
|
||
const instanceId = message.data && message.data.instanceId; | ||
if (instanceId && view) { | ||
const target: View | undefined = runtime.getView(instanceId); | ||
if (target) { | ||
//add a listener for the specific target (instanceId) | ||
target.listeners.push({ | ||
viewId: view.id, | ||
source: instanceId, | ||
listenerId: (message.data && message.data.id) || '', | ||
contextType: (message.data && message.data.contextType) || '', | ||
}); | ||
const pendingContexts = target.getPendingContexts(); | ||
if (pendingContexts && pendingContexts.length > 0) { | ||
pendingContexts.forEach((pending, i) => { | ||
//does the source of the pending context match the target? | ||
if (pending && pending.source && pending.source === view.id) { | ||
//is there a match on contextType (if specified...) | ||
if ( | ||
pending.context && | ||
pending.context.type && | ||
pending.context.type === message.data && | ||
message.data.type | ||
) { | ||
console.log('send pending context'); | ||
view.content.webContents.postMessage(FDC3_TOPICS.CONTEXT, { | ||
topic: FDC3_TOPICS.CONTEXT, | ||
data: pending.context, | ||
source: source, | ||
}); | ||
target.removePendingContext(i); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
//use channel from the event message first, or use the channel of the sending app, or use default | ||
const channel: string = | ||
message.data && message.data.channel | ||
? message.data.channel | ||
: view && view.channel | ||
? view.channel | ||
: 'default'; //: (c && c.channel) ? c.channel | ||
|
||
if (view) { | ||
view.listeners.push({ | ||
listenerId: (message.data && message.data.id) || '', | ||
viewId: view.id, | ||
contextType: (message.data && message.data.contextType) || undefined, | ||
channel: channel, | ||
isChannel: channel !== 'default', | ||
}); | ||
|
||
/* | ||
are there any pending contexts for the listener just added? | ||
*/ | ||
const pending = view.getPendingContexts(); | ||
if (pending && pending.length > 0) { | ||
pending.forEach((pending: Pending, i: number) => { | ||
//is there a match on contextType (if specified...) | ||
|
||
if ( | ||
message.data === undefined || | ||
(message.data && message.data.type === undefined) || | ||
(pending.context && | ||
pending.context.type && | ||
pending.context.type === message.data && | ||
message.data.type) | ||
) { | ||
view.content.webContents.send(FDC3_TOPICS.CONTEXT, { | ||
topic: 'context', | ||
listenerId: message.data && message.data.id, | ||
data: { | ||
context: pending.context, | ||
listenerId: message.data && message.data.id, | ||
}, | ||
source: source, | ||
}); | ||
|
||
view.removePendingContext(i); | ||
} | ||
}); | ||
} | ||
} | ||
}; |
Oops, something went wrong.