-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathHelloHandler.ts
162 lines (139 loc) · 5.85 KB
/
HelloHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { FDC3_VERSION, GetAgentParams, WebDesktopAgentType } from '@finos/fdc3-standard';
import { ConnectionDetails } from '../messaging/MessagePortMessaging';
import { Logger } from '../util/Logger';
import { BrowserTypes } from '@finos/fdc3-schema';
const { isWebConnectionProtocol2LoadURL, isWebConnectionProtocol3Handshake } = BrowserTypes;
type WebConnectionProtocolMessage = BrowserTypes.WebConnectionProtocolMessage;
type WebConnectionProtocol1Hello = BrowserTypes.WebConnectionProtocol1Hello;
export class HelloHandler {
constructor(
options: GetAgentParams,
connectionAttemptUuid: string,
agentType: WebDesktopAgentType = WebDesktopAgentType.ProxyParent
) {
this.options = options;
this.connectionAttemptUuid = connectionAttemptUuid;
this.agentType = agentType;
this.helloResponseListener = null;
}
/** Parameters passed to getAgent */
options: GetAgentParams;
/** UUID used to filter messages */
connectionAttemptUuid: string;
/** The agentType to set, which may change if we're asked to load a URL into an iframe */
agentType: WebDesktopAgentType;
/** If we're asked to load a URL into an iframe, it is stored here to be saved in Session Storage */
agentUrl: string | null = null;
/** Reference to event listener used for responses from Desktop Agents -
* Used to remove them when no longer needed.
* Initialized when
* - listening for hello responses
* - listening for identity validation responses
* */
helloResponseListener: ((event: MessageEvent<WebConnectionProtocolMessage>) => void) | null;
/**
* Starts the connection process off by sending a hello message
*/
sendWCP1Hello(w: MessageEventSource, origin: string) {
const requestMessage: WebConnectionProtocol1Hello = {
type: 'WCP1Hello',
meta: {
connectionAttemptUuid: this.connectionAttemptUuid,
timestamp: new Date(),
},
payload: {
channelSelector: this.options.channelSelector,
fdc3Version: FDC3_VERSION,
resolver: this.options.intentResolver,
identityUrl: this.options.identityUrl!,
actualUrl: globalThis.window.location.href,
},
};
Logger.debug(`HelloHandler: Sending hello msg:\n${JSON.stringify(requestMessage, null, 2)}`);
w.postMessage(requestMessage, { targetOrigin: origin });
}
/**
* Handle a request from a desktop agent that the client loads an adaptor URL
* into an iframe instead of working with the parent window.
*/
openFrame(url: string) {
const IFRAME_ID = 'fdc3-communications-embedded-iframe';
// remove an old one if it's there
document.getElementById(IFRAME_ID)?.remove();
//note the iframe URL and desktop agent type have changed
this.agentType = WebDesktopAgentType.ProxyUrl;
this.agentUrl = url;
// create a new one
const iframe = document.createElement('iframe');
//Wait for the iframe to load... then send it a hello message
iframe.addEventListener('load', () => {
if (iframe.contentWindow) {
Logger.debug('Sending hello message to communication iframe');
this.sendWCP1Hello(iframe.contentWindow, '*');
} else {
throw new Error(
`An iframe (url: ${url}) added to support communication with a Desktop Agent does not have a contentWindow, despite firing its load event!`
);
}
});
iframe.setAttribute('src', url);
iframe.setAttribute('id', IFRAME_ID);
iframe.setAttribute('name', 'FDC3 Communications');
iframe.style.width = '0px';
iframe.style.height = '0px';
iframe.style.border = '0';
iframe.style.position = 'fixed';
document.body.appendChild(iframe);
}
/** Listen for WCP responses from 'parent' windows and frames and handle them.
* Resolves when a response is received.
* @returns A Promise resolving to a set of ConnectionDetails
*/
listenForHelloResponses(): Promise<ConnectionDetails> {
return new Promise<ConnectionDetails>(resolve => {
// setup listener for message and retrieve JS URL from it
this.helloResponseListener = (event: MessageEvent<WebConnectionProtocolMessage>) => {
const data = event.data;
if (data?.meta?.connectionAttemptUuid == this.connectionAttemptUuid) {
if (isWebConnectionProtocol2LoadURL(data)) {
// in this case, we need to load the URL with the embedded Iframe
const url = data.payload.iframeUrl;
this.openFrame(url);
//n.b event listener remains in place to receive messages from the iframe
} else if (isWebConnectionProtocol3Handshake(data)) {
Logger.debug(`HelloHandler: successful handshake`);
resolve({
connectionAttemptUuid: this.connectionAttemptUuid,
handshake: data,
messagePort: event.ports[0],
options: this.options,
actualUrl: globalThis.window.location.href,
agentType: this.agentType,
agentUrl: this.agentUrl ?? undefined,
});
//remove the event listener as we've received a messagePort to use
this.cancel();
} else {
Logger.debug(
`Ignoring unexpected message in HelloHandler (because its not WCP2LoadUrl or WCP3Handshake).`,
data
);
}
} else {
Logger.warn(
`HelloHandler: Ignoring message with invalid connectionAttemptUuid. Expected ${this.connectionAttemptUuid}, received: ${data?.meta?.connectionAttemptUuid}`,
data
);
}
};
globalThis.window.addEventListener('message', this.helloResponseListener);
});
}
/** Removes listeners so that events are no longer processed */
cancel() {
if (this.helloResponseListener) {
globalThis.window.removeEventListener('message', this.helloResponseListener);
this.helloResponseListener = null;
}
}
}