@openfin/cloud-interop-core-api 0.0.1-alpha.e8aa2c9 → 0.0.1-alpha.e96a3d8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle.d.ts +14 -4
- package/index.cjs +43 -23
- package/index.mjs +43 -23
- package/package.json +5 -5
package/bundle.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ declare const appIntentSchema: z.ZodObject<{
|
|
|
51
51
|
name: string;
|
|
52
52
|
displayName: string;
|
|
53
53
|
}>;
|
|
54
|
-
apps: z.ZodArray<z.ZodObject<
|
|
54
|
+
apps: z.ZodArray<z.ZodObject<{
|
|
55
55
|
description: z.ZodOptional<z.ZodString>;
|
|
56
56
|
icons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
57
57
|
size: z.ZodOptional<z.ZodString>;
|
|
@@ -88,10 +88,10 @@ declare const appIntentSchema: z.ZodObject<{
|
|
|
88
88
|
title: z.ZodOptional<z.ZodString>;
|
|
89
89
|
tooltip: z.ZodOptional<z.ZodString>;
|
|
90
90
|
version: z.ZodOptional<z.ZodString>;
|
|
91
|
-
}
|
|
91
|
+
} & {
|
|
92
92
|
appId: z.ZodString;
|
|
93
93
|
instanceId: z.ZodOptional<z.ZodString>;
|
|
94
|
-
}
|
|
94
|
+
}, "strip", z.ZodTypeAny, {
|
|
95
95
|
appId: string;
|
|
96
96
|
instanceId?: string | undefined;
|
|
97
97
|
description?: string | undefined;
|
|
@@ -268,7 +268,7 @@ export declare class CloudInteropAPI {
|
|
|
268
268
|
* @returns {*} {Promise<void>}
|
|
269
269
|
* @memberof CloudInteropAPI
|
|
270
270
|
*/
|
|
271
|
-
setContext(contextGroup: string, context:
|
|
271
|
+
setContext(contextGroup: string, context: InferredContext): Promise<void>;
|
|
272
272
|
/**
|
|
273
273
|
* Starts an intent discovery operation
|
|
274
274
|
*
|
|
@@ -420,6 +420,14 @@ export declare type CreateSessionResponse = {
|
|
|
420
420
|
sourceId: string;
|
|
421
421
|
};
|
|
422
422
|
|
|
423
|
+
declare const errorSchema: z.ZodObject<{
|
|
424
|
+
error: z.ZodString;
|
|
425
|
+
}, "strip", z.ZodTypeAny, {
|
|
426
|
+
error: string;
|
|
427
|
+
}, {
|
|
428
|
+
error: string;
|
|
429
|
+
}>;
|
|
430
|
+
|
|
423
431
|
export declare type EventListenersMap = Map<keyof EventMap, Array<(...args: Parameters<EventMap[keyof EventMap]>) => void>>;
|
|
424
432
|
|
|
425
433
|
export declare type EventMap = {
|
|
@@ -545,6 +553,8 @@ declare type HistoryRecord = {
|
|
|
545
553
|
*/
|
|
546
554
|
export declare type InferredContext = z.infer<typeof contextSchema>;
|
|
547
555
|
|
|
556
|
+
export declare type InferredError = z.infer<typeof errorSchema>;
|
|
557
|
+
|
|
548
558
|
/**
|
|
549
559
|
* @internal
|
|
550
560
|
*/
|
package/index.cjs
CHANGED
|
@@ -46,6 +46,8 @@ class EventController {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const isErrorIntentResult = (result) => 'error' in result;
|
|
50
|
+
|
|
49
51
|
const APP_ID_DELIM = '::';
|
|
50
52
|
const getRequestHeaders = (connectionParameters) => {
|
|
51
53
|
const headers = {};
|
|
@@ -72,13 +74,9 @@ const getRequestHeaders = (connectionParameters) => {
|
|
|
72
74
|
* @param source
|
|
73
75
|
* @returns
|
|
74
76
|
*/
|
|
75
|
-
const encodeAppIntents = (appIntents,
|
|
77
|
+
const encodeAppIntents = (appIntents, source) => appIntents.map((intent) => ({
|
|
76
78
|
...intent,
|
|
77
|
-
apps: intent.apps.map((app) => {
|
|
78
|
-
const id = encodeURIComponent(app.appId);
|
|
79
|
-
const sId = encodeURIComponent(sourceId);
|
|
80
|
-
return { ...app, appId: `${id}${APP_ID_DELIM}${sId}${APP_ID_DELIM}${sessionId}` };
|
|
81
|
-
}),
|
|
79
|
+
apps: intent.apps.map((app) => ({ ...app, appId: encodeAppId(app.appId, source) })),
|
|
82
80
|
}));
|
|
83
81
|
/**
|
|
84
82
|
* Decodes all app intents by URI decoding the parts previously encoded by `encodeAppIntents`
|
|
@@ -87,13 +85,19 @@ const encodeAppIntents = (appIntents, { sessionId, sourceId }) => appIntents.map
|
|
|
87
85
|
*/
|
|
88
86
|
const decodeAppIntents = (appIntents) => appIntents.map((intent) => ({
|
|
89
87
|
...intent,
|
|
90
|
-
apps: intent.apps.map((app) => {
|
|
91
|
-
const [encodedAppId, encodedSourceId, sessionId] = app.appId.split(APP_ID_DELIM);
|
|
92
|
-
const id = decodeURIComponent(encodedAppId);
|
|
93
|
-
const sourceId = decodeURIComponent(encodedSourceId);
|
|
94
|
-
return { ...app, appId: `${id}${APP_ID_DELIM}${sourceId}${APP_ID_DELIM}${sessionId}` };
|
|
95
|
-
}),
|
|
88
|
+
apps: intent.apps.map((app) => ({ ...app, appId: decodeAppId(app.appId) })),
|
|
96
89
|
}));
|
|
90
|
+
const encodeAppId = (appIdString, { sessionId, sourceId }) => {
|
|
91
|
+
const id = encodeURIComponent(appIdString);
|
|
92
|
+
const sId = encodeURIComponent(sourceId);
|
|
93
|
+
return `${id}${APP_ID_DELIM}${sId}${APP_ID_DELIM}${sessionId}`;
|
|
94
|
+
};
|
|
95
|
+
const decodeAppId = (appId) => {
|
|
96
|
+
const [encodedAppId, encodedSourceId, sessionId] = appId.split(APP_ID_DELIM);
|
|
97
|
+
const id = decodeURIComponent(encodedAppId);
|
|
98
|
+
const sourceId = decodeURIComponent(encodedSourceId);
|
|
99
|
+
return `${id}${APP_ID_DELIM}${sourceId}${APP_ID_DELIM}${sessionId}`;
|
|
100
|
+
};
|
|
97
101
|
/**
|
|
98
102
|
* Decodes the AppIdentifier to extract the appId, sourceId, and sessionId.
|
|
99
103
|
* @returns an object with:
|
|
@@ -158,13 +162,18 @@ class IntentController {
|
|
|
158
162
|
body: JSON.stringify({ findOptions }),
|
|
159
163
|
});
|
|
160
164
|
if (!startResponse.ok) {
|
|
161
|
-
throw new Error(startResponse.statusText);
|
|
165
|
+
throw new Error(`Error creating intent discovery record: ${startResponse.statusText}`);
|
|
162
166
|
}
|
|
163
167
|
// TODO: type this response?
|
|
164
168
|
const json = await startResponse.json();
|
|
165
169
|
this.#discovery.id = json.discoveryId;
|
|
166
170
|
this.#discovery.sessionCount = json.sessionCount;
|
|
167
171
|
this.#discovery.state = 'in-progress';
|
|
172
|
+
if (this.#discovery.sessionCount === 1) {
|
|
173
|
+
// since we have no other connected sessions, we can end discovery immediately
|
|
174
|
+
await this.#endIntentDiscovery(false);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
168
177
|
// Listen out for discovery results directly sent to us
|
|
169
178
|
await this.#mqttClient.subscribeAsync(`${this.#sessionDetails.sessionRootTopic}/commands/${this.#discovery.id}`);
|
|
170
179
|
this.#discoveryTimeout = setTimeout(() => this.#endIntentDiscovery(), clampedTimeout);
|
|
@@ -175,10 +184,8 @@ class IntentController {
|
|
|
175
184
|
throw new CloudInteropAPIError('Error starting intent discovery', 'ERR_STARTING_INTENT_DISCOVERY', error);
|
|
176
185
|
}
|
|
177
186
|
}
|
|
178
|
-
async #endIntentDiscovery() {
|
|
187
|
+
async #endIntentDiscovery(mqttUnsubscribe = true) {
|
|
179
188
|
if (this.#discovery.state !== 'in-progress') {
|
|
180
|
-
// TODO: remove debug logs
|
|
181
|
-
this.#logger('debug', 'Intent discovery not in progress');
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
191
|
if (this.#discoveryTimeout) {
|
|
@@ -188,10 +195,12 @@ class IntentController {
|
|
|
188
195
|
this.#discovery.state = 'ended';
|
|
189
196
|
// emit our aggregated events
|
|
190
197
|
this.#events.emitEvent('aggregate-intent-details', { responses: this.#discovery.pendingIntentDetailsEvents });
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.#
|
|
194
|
-
|
|
198
|
+
if (mqttUnsubscribe) {
|
|
199
|
+
// gracefully end discovery
|
|
200
|
+
await this.#mqttClient.unsubscribeAsync(`${this.#sessionDetails.sessionRootTopic}/commands/${this.#discovery.id}`).catch(() => {
|
|
201
|
+
this.#logger('warn', `Error ending intent discovery: could not unsubscribe from discovery id ${this.#discovery.id}`);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
195
204
|
await fetch(`${this.#url}/api/intents/${this.#sessionDetails.sessionId}/${this.#discovery.id}`, {
|
|
196
205
|
method: 'DELETE',
|
|
197
206
|
headers: getRequestHeaders(this.#connectionParams),
|
|
@@ -200,7 +209,6 @@ class IntentController {
|
|
|
200
209
|
if (!deleteResponse.ok) {
|
|
201
210
|
throw new Error(`Error ending intent discovery: ${deleteResponse.statusText}`);
|
|
202
211
|
}
|
|
203
|
-
this.#logger('debug', 'Intent discovery ended');
|
|
204
212
|
})
|
|
205
213
|
.catch((error) => {
|
|
206
214
|
this.#logger('warn', `Error ending intent discovery: ${error}`);
|
|
@@ -244,6 +252,13 @@ class IntentController {
|
|
|
244
252
|
return false;
|
|
245
253
|
}
|
|
246
254
|
async sendIntentResult(initiatingSessionId, result) {
|
|
255
|
+
if (!isErrorIntentResult(result)) {
|
|
256
|
+
// cloud-encode the source app id to support chained intent actions over cloud
|
|
257
|
+
// https://fdc3.finos.org/docs/2.0/api/spec#resolution-object -> "Use metadata about the resolving app instance to target a further intent"
|
|
258
|
+
const source = getSourceFromSession(this.#sessionDetails);
|
|
259
|
+
const encoded = encodeAppId(typeof result.source === 'string' ? result.source : result.source.appId, source);
|
|
260
|
+
result.source = typeof result.source === 'string' ? encoded : { ...result.source, appId: encoded };
|
|
261
|
+
}
|
|
247
262
|
const { sessionId } = getSourceFromSession(this.#sessionDetails);
|
|
248
263
|
const resultResponse = await fetch(`${this.#url}/api/intents/${initiatingSessionId}/result/${sessionId}`, {
|
|
249
264
|
method: 'POST',
|
|
@@ -580,8 +595,13 @@ class CloudInteropAPI {
|
|
|
580
595
|
if (contextEvent.source.sessionId === sessionDetails.sessionId) {
|
|
581
596
|
return;
|
|
582
597
|
}
|
|
583
|
-
const { contextGroup,
|
|
584
|
-
this.#events.emitEvent('context', {
|
|
598
|
+
const { context, payload, contextGroup, channelName, source, history } = contextEvent;
|
|
599
|
+
this.#events.emitEvent('context', {
|
|
600
|
+
contextGroup: channelName || contextGroup,
|
|
601
|
+
context: payload || context,
|
|
602
|
+
source,
|
|
603
|
+
history: { ...history, clientReceived: Date.now() },
|
|
604
|
+
});
|
|
585
605
|
}
|
|
586
606
|
else if (topic.startsWith(`${sessionDetails.sessionRootTopic}/commands`)) {
|
|
587
607
|
this.#handleCommandMessage(messageEnvelope);
|
package/index.mjs
CHANGED
|
@@ -44,6 +44,8 @@ class EventController {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const isErrorIntentResult = (result) => 'error' in result;
|
|
48
|
+
|
|
47
49
|
const APP_ID_DELIM = '::';
|
|
48
50
|
const getRequestHeaders = (connectionParameters) => {
|
|
49
51
|
const headers = {};
|
|
@@ -70,13 +72,9 @@ const getRequestHeaders = (connectionParameters) => {
|
|
|
70
72
|
* @param source
|
|
71
73
|
* @returns
|
|
72
74
|
*/
|
|
73
|
-
const encodeAppIntents = (appIntents,
|
|
75
|
+
const encodeAppIntents = (appIntents, source) => appIntents.map((intent) => ({
|
|
74
76
|
...intent,
|
|
75
|
-
apps: intent.apps.map((app) => {
|
|
76
|
-
const id = encodeURIComponent(app.appId);
|
|
77
|
-
const sId = encodeURIComponent(sourceId);
|
|
78
|
-
return { ...app, appId: `${id}${APP_ID_DELIM}${sId}${APP_ID_DELIM}${sessionId}` };
|
|
79
|
-
}),
|
|
77
|
+
apps: intent.apps.map((app) => ({ ...app, appId: encodeAppId(app.appId, source) })),
|
|
80
78
|
}));
|
|
81
79
|
/**
|
|
82
80
|
* Decodes all app intents by URI decoding the parts previously encoded by `encodeAppIntents`
|
|
@@ -85,13 +83,19 @@ const encodeAppIntents = (appIntents, { sessionId, sourceId }) => appIntents.map
|
|
|
85
83
|
*/
|
|
86
84
|
const decodeAppIntents = (appIntents) => appIntents.map((intent) => ({
|
|
87
85
|
...intent,
|
|
88
|
-
apps: intent.apps.map((app) => {
|
|
89
|
-
const [encodedAppId, encodedSourceId, sessionId] = app.appId.split(APP_ID_DELIM);
|
|
90
|
-
const id = decodeURIComponent(encodedAppId);
|
|
91
|
-
const sourceId = decodeURIComponent(encodedSourceId);
|
|
92
|
-
return { ...app, appId: `${id}${APP_ID_DELIM}${sourceId}${APP_ID_DELIM}${sessionId}` };
|
|
93
|
-
}),
|
|
86
|
+
apps: intent.apps.map((app) => ({ ...app, appId: decodeAppId(app.appId) })),
|
|
94
87
|
}));
|
|
88
|
+
const encodeAppId = (appIdString, { sessionId, sourceId }) => {
|
|
89
|
+
const id = encodeURIComponent(appIdString);
|
|
90
|
+
const sId = encodeURIComponent(sourceId);
|
|
91
|
+
return `${id}${APP_ID_DELIM}${sId}${APP_ID_DELIM}${sessionId}`;
|
|
92
|
+
};
|
|
93
|
+
const decodeAppId = (appId) => {
|
|
94
|
+
const [encodedAppId, encodedSourceId, sessionId] = appId.split(APP_ID_DELIM);
|
|
95
|
+
const id = decodeURIComponent(encodedAppId);
|
|
96
|
+
const sourceId = decodeURIComponent(encodedSourceId);
|
|
97
|
+
return `${id}${APP_ID_DELIM}${sourceId}${APP_ID_DELIM}${sessionId}`;
|
|
98
|
+
};
|
|
95
99
|
/**
|
|
96
100
|
* Decodes the AppIdentifier to extract the appId, sourceId, and sessionId.
|
|
97
101
|
* @returns an object with:
|
|
@@ -156,13 +160,18 @@ class IntentController {
|
|
|
156
160
|
body: JSON.stringify({ findOptions }),
|
|
157
161
|
});
|
|
158
162
|
if (!startResponse.ok) {
|
|
159
|
-
throw new Error(startResponse.statusText);
|
|
163
|
+
throw new Error(`Error creating intent discovery record: ${startResponse.statusText}`);
|
|
160
164
|
}
|
|
161
165
|
// TODO: type this response?
|
|
162
166
|
const json = await startResponse.json();
|
|
163
167
|
this.#discovery.id = json.discoveryId;
|
|
164
168
|
this.#discovery.sessionCount = json.sessionCount;
|
|
165
169
|
this.#discovery.state = 'in-progress';
|
|
170
|
+
if (this.#discovery.sessionCount === 1) {
|
|
171
|
+
// since we have no other connected sessions, we can end discovery immediately
|
|
172
|
+
await this.#endIntentDiscovery(false);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
166
175
|
// Listen out for discovery results directly sent to us
|
|
167
176
|
await this.#mqttClient.subscribeAsync(`${this.#sessionDetails.sessionRootTopic}/commands/${this.#discovery.id}`);
|
|
168
177
|
this.#discoveryTimeout = setTimeout(() => this.#endIntentDiscovery(), clampedTimeout);
|
|
@@ -173,10 +182,8 @@ class IntentController {
|
|
|
173
182
|
throw new CloudInteropAPIError('Error starting intent discovery', 'ERR_STARTING_INTENT_DISCOVERY', error);
|
|
174
183
|
}
|
|
175
184
|
}
|
|
176
|
-
async #endIntentDiscovery() {
|
|
185
|
+
async #endIntentDiscovery(mqttUnsubscribe = true) {
|
|
177
186
|
if (this.#discovery.state !== 'in-progress') {
|
|
178
|
-
// TODO: remove debug logs
|
|
179
|
-
this.#logger('debug', 'Intent discovery not in progress');
|
|
180
187
|
return;
|
|
181
188
|
}
|
|
182
189
|
if (this.#discoveryTimeout) {
|
|
@@ -186,10 +193,12 @@ class IntentController {
|
|
|
186
193
|
this.#discovery.state = 'ended';
|
|
187
194
|
// emit our aggregated events
|
|
188
195
|
this.#events.emitEvent('aggregate-intent-details', { responses: this.#discovery.pendingIntentDetailsEvents });
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.#
|
|
192
|
-
|
|
196
|
+
if (mqttUnsubscribe) {
|
|
197
|
+
// gracefully end discovery
|
|
198
|
+
await this.#mqttClient.unsubscribeAsync(`${this.#sessionDetails.sessionRootTopic}/commands/${this.#discovery.id}`).catch(() => {
|
|
199
|
+
this.#logger('warn', `Error ending intent discovery: could not unsubscribe from discovery id ${this.#discovery.id}`);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
193
202
|
await fetch(`${this.#url}/api/intents/${this.#sessionDetails.sessionId}/${this.#discovery.id}`, {
|
|
194
203
|
method: 'DELETE',
|
|
195
204
|
headers: getRequestHeaders(this.#connectionParams),
|
|
@@ -198,7 +207,6 @@ class IntentController {
|
|
|
198
207
|
if (!deleteResponse.ok) {
|
|
199
208
|
throw new Error(`Error ending intent discovery: ${deleteResponse.statusText}`);
|
|
200
209
|
}
|
|
201
|
-
this.#logger('debug', 'Intent discovery ended');
|
|
202
210
|
})
|
|
203
211
|
.catch((error) => {
|
|
204
212
|
this.#logger('warn', `Error ending intent discovery: ${error}`);
|
|
@@ -242,6 +250,13 @@ class IntentController {
|
|
|
242
250
|
return false;
|
|
243
251
|
}
|
|
244
252
|
async sendIntentResult(initiatingSessionId, result) {
|
|
253
|
+
if (!isErrorIntentResult(result)) {
|
|
254
|
+
// cloud-encode the source app id to support chained intent actions over cloud
|
|
255
|
+
// https://fdc3.finos.org/docs/2.0/api/spec#resolution-object -> "Use metadata about the resolving app instance to target a further intent"
|
|
256
|
+
const source = getSourceFromSession(this.#sessionDetails);
|
|
257
|
+
const encoded = encodeAppId(typeof result.source === 'string' ? result.source : result.source.appId, source);
|
|
258
|
+
result.source = typeof result.source === 'string' ? encoded : { ...result.source, appId: encoded };
|
|
259
|
+
}
|
|
245
260
|
const { sessionId } = getSourceFromSession(this.#sessionDetails);
|
|
246
261
|
const resultResponse = await fetch(`${this.#url}/api/intents/${initiatingSessionId}/result/${sessionId}`, {
|
|
247
262
|
method: 'POST',
|
|
@@ -578,8 +593,13 @@ class CloudInteropAPI {
|
|
|
578
593
|
if (contextEvent.source.sessionId === sessionDetails.sessionId) {
|
|
579
594
|
return;
|
|
580
595
|
}
|
|
581
|
-
const { contextGroup,
|
|
582
|
-
this.#events.emitEvent('context', {
|
|
596
|
+
const { context, payload, contextGroup, channelName, source, history } = contextEvent;
|
|
597
|
+
this.#events.emitEvent('context', {
|
|
598
|
+
contextGroup: channelName || contextGroup,
|
|
599
|
+
context: payload || context,
|
|
600
|
+
source,
|
|
601
|
+
history: { ...history, clientReceived: Date.now() },
|
|
602
|
+
});
|
|
583
603
|
}
|
|
584
604
|
else if (topic.startsWith(`${sessionDetails.sessionRootTopic}/commands`)) {
|
|
585
605
|
this.#handleCommandMessage(messageEnvelope);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfin/cloud-interop-core-api",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.e96a3d8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "./index.cjs",
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"types": "./bundle.d.ts",
|
|
9
9
|
"author": "",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"mqtt": "^5.13.0",
|
|
13
|
+
"zod": "^3.24.4"
|
|
14
|
+
},
|
|
11
15
|
"optionalDependencies": {
|
|
12
16
|
"@rollup/rollup-linux-x64-gnu": "*"
|
|
13
|
-
},
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"mqtt": "^5.3.1",
|
|
16
|
-
"zod": "^3.24.1"
|
|
17
17
|
}
|
|
18
18
|
}
|