-
Notifications
You must be signed in to change notification settings - Fork 135
/
Copy pathPostMessageLoader.ts
183 lines (152 loc) · 6.39 KB
/
PostMessageLoader.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { AgentError, AppIdentifier, DEFAULT_TIMEOUT_MS, GetAgentParams } from '@finos/fdc3-standard';
import { createDesktopAgentAPI } from '../messaging/message-port';
import { v4 as uuidv4 } from 'uuid';
import { DesktopAgentSelection, Loader } from './Loader';
import { HelloHandler } from './HelloHandler';
import { IdentityValidationHandler } from './IdentityValidationHandler';
import { Logger } from '../util/Logger';
/**
* Recursive search for all possible parent frames (windows) that we may
* target with the WCP.
* @param startWindow window object to search
* @param found window objects found so far
*/
function collectPossibleTargets(startWindow: Window, found: Window[]) {
_recursePossibleTargets(startWindow, startWindow, found);
Logger.debug(`Possible parent windows/frames found: ${found.length}`);
}
function _recursePossibleTargets(startWindow: Window, w: Window, found: Window[]) {
if (w) {
if (found.indexOf(w) == -1 && w != startWindow) {
found.push(w);
}
if (w.opener) {
_recursePossibleTargets(startWindow, w.opener, found);
}
if (w.parent != w) {
_recursePossibleTargets(startWindow, w.parent, found);
}
}
}
/** Loader for Desktop Agent Proxy implementations. Attempts to
* connect to parent windows or frames via teh Web Connection Protocol,
* which may include setting up an iframe to load an adaptor URL.
* A previously persisted adaptor URL may be passed to skip the
* discovery of parent windows and to move straight to loading that.
*/
export class PostMessageLoader implements Loader {
name = 'PostMessageLoader';
constructor(previousUrl?: string) {
this.previousUrl = previousUrl ?? null;
}
previousUrl: string | null;
connectionAttemptUuid = uuidv4();
helloHandler?: HelloHandler;
identityValidationHandler?: IdentityValidationHandler;
/** Initial timeout (released once a MessagePort is received - additional steps are outside timeout) */
timeout: NodeJS.Timeout | null = null;
/** Reference to the get fn's Promise's reject call - used when cancelling. */
rejectFn: ((reason?: string) => void) | null = null;
get(options: GetAgentParams): Promise<DesktopAgentSelection> {
Logger.debug(`PostMessageLoader.get(): Initiating search for Desktop Agent Proxy`);
return new Promise<DesktopAgentSelection>((resolve, reject) => {
//save reject fn in case we get cancelled
this.rejectFn = reject;
//setup a timeout so we can reject if it runs out
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
this.timeout = setTimeout(() => {
Logger.debug(`PostMessageLoader.get(): timeout (${timeoutMs} ms) at ${new Date().toISOString()}`);
this.cancel();
reject(AgentError.AgentNotFound);
}, timeoutMs);
this.helloHandler = new HelloHandler(options, this.connectionAttemptUuid);
// ok, begin the process
const handshakePromise = this.helloHandler.listenForHelloResponses();
if (this.previousUrl) {
Logger.debug(`PostMessageLoader.get(): Loading previously used adaptor URL: ${this.previousUrl}`);
//skip looking for target parent windows and open an iframe immediately
this.helloHandler.openFrame(this.previousUrl);
} else {
//collect target parent window references
const targets: Window[] = [];
collectPossibleTargets(globalThis.window, targets);
// use of origin '*': See https://github.com/finos/FDC3/issues/1316
for (let t = 0; t < targets.length; t++) {
this.helloHandler.sendWCP1Hello(targets[t], '*');
}
}
// wait for one of the windows to respond
// This may involve a WCP2LoadUrl response being received
// and an adaptor iframe setup to load it, resolves on
// WCP3Handshake response.
// If no WCP3Handshake is ever received this will not resolve
handshakePromise.then(connectionDetails => {
//prevent us being cancelled
this.rejectFn = null;
//cancel the initial timeout as we got a handshake response
if (this.timeout) {
clearTimeout(this.timeout);
}
//perform id validation
this.identityValidationHandler = new IdentityValidationHandler(
connectionDetails.messagePort,
options,
this.connectionAttemptUuid
);
const idValidationPromise = this.identityValidationHandler.listenForIDValidationResponses();
//start the message port so that we can receive responses
connectionDetails.messagePort.start();
this.identityValidationHandler.sendIdValidationMessage();
idValidationPromise
.then(idDetails => {
//resolve
const appIdentifier: AppIdentifier = {
appId: idDetails.payload.appId,
instanceId: idDetails.payload.instanceId,
};
createDesktopAgentAPI(connectionDetails, appIdentifier).then(da => {
const desktopAgentSelection: DesktopAgentSelection = {
agent: da,
details: {
agentType: connectionDetails.agentType,
agentUrl: connectionDetails.agentUrl ?? undefined,
identityUrl: connectionDetails.options.identityUrl ?? connectionDetails.actualUrl,
actualUrl: connectionDetails.actualUrl,
appId: idDetails.payload.appId,
instanceId: idDetails.payload.instanceId,
instanceUuid: idDetails.payload.instanceUuid,
},
};
//clean up
this.cancel();
resolve(desktopAgentSelection);
});
})
.catch(e => {
//id validation may have failed
reject(e);
});
});
});
}
async cancel(): Promise<void> {
Logger.debug('PostMessageLoader: Cleaning up');
//if we're being cancelled while still running, reject the promise
if (this.rejectFn) {
this.rejectFn(AgentError.AgentNotFound);
this.rejectFn = null;
}
//cancel the timeout
if (this.timeout) {
clearTimeout(this.timeout);
}
//remove any event listeners to end processing
if (this.helloHandler) {
this.helloHandler.cancel();
}
//TODO Decide if we should NOT do this - there may be a race on timeout cancellations
if (this.identityValidationHandler) {
this.identityValidationHandler.cancel();
}
}
}