Skip to content

Commit

Permalink
Merge pull request #65 from nkolba/fdc3-handlers
Browse files Browse the repository at this point in the history
initial commit for fdc3 handlers refactor
  • Loading branch information
nkolba authored Oct 11, 2022
2 parents dfcabdd + bea7edb commit 8c6c225
Show file tree
Hide file tree
Showing 30 changed files with 2,104 additions and 2,499 deletions.
599 changes: 301 additions & 298 deletions package-lock.json

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions packages/main/src/handlers/fdc3/1.2/broadcast.ts
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,
});
}
});
}
};
115 changes: 115 additions & 0 deletions packages/main/src/handlers/fdc3/1.2/channels.ts
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 packages/main/src/handlers/fdc3/1.2/contextListeners.ts
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);
}
});
}
}
};
Loading

0 comments on commit 8c6c225

Please sign in to comment.