@openfin/cloud-interop 0.40.38 → 0.40.39

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.
Files changed (3) hide show
  1. package/README.md +20 -1
  2. package/out/index.js +101 -51
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,26 @@ This package contains an override that enables Cloud Interop for `InteropBroker`
4
4
 
5
5
  Overrides are passed to `fin.Platform.init` when initializing a platform or to `fin.Interop.init` when creating an `InteropBroker` directly.
6
6
 
7
- Once overriden the `InteropBroker` will connect to the given Cloud Interop Service upon initialization. `Context` will be sent to the Cloud Interop server and the broker will receive `Context` updates from other any other connected brokers in the shared session.
7
+ Once overriden the `InteropBroker` will connect to the given Cloud Interop Service upon initialization. `Context` will be sent to the Cloud Interop Service when the override broker calls `setContext` and the broker will also subscribe `Context` updates from other any other connected brokers in the shared session.
8
+
9
+ The Cloud Interop Service also supports `Intent`'s. Connected `InteropBroker`'s info calls (`handleInfoForIntent`, `handleInfoForIntentsByContext`, `fdc3.findIntent`, `fdc3.findIntentsByContext`) will include results for remote sessions and decorate those app identifiers with additional metadata which can then be used to raise `Intent`'s on remote sessions.
10
+
11
+ ## Override Options
12
+
13
+ | Option | Type | Description | Default Value | Required |
14
+ |------------------------------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------|-------------------|----------|
15
+ | `url` | `string` | URL of the Cloud Interop service | N/A | Yes |
16
+ | `platformId` | `string` | Platform ID | N/A | Yes |
17
+ | `sourceId` | `string` | Source ID | N/A | Yes |
18
+ | `reconnectRetryLimit` | `number` (optional) | Limit for reconnect retries | N/A | No |
19
+ | `keepAliveIntervalSeconds` | `number` (optional) | Interval in seconds to keep the connection alive | N/A | No |
20
+ | `findIntentTimeout` | `number` (optional) | How long to wait for connected clients to respond. Do not make this too high. | `3 seconds` | No |
21
+ | `authenticationType` | ``'jwt' \| 'basic' \| 'default'`` (optional) | Type of authentication to use | N/A | No |
22
+ | `basicAuthenticationParameters` | `{ username: string, password: string }` (optional) | Parameters for basic authentication | N/A | No |
23
+ | `jwtAuthenticationParameters` | `{ jwtRequestCallback: () => string \| object, authenticationId: string }` (optional) | Parameters for JWT authentication | N/A | No |
24
+ | `logger` | ``(logLevel: 'log' \| 'debug' \| 'info' \| 'warn' \| 'error', message: string, ...args: unknown[]) => void`` (optional) | If provided, will defer to this logger, otherwise logs to `console.debug`, reduced by LogLevel | `console.debug` | No |
25
+ | `logLevel` | ``'log' \| 'debug' \| 'info' \| 'warn' \| 'error'`` (optional) | The level at which cloud-interop override will log. | `'warn'` | No |
26
+
8
27
 
9
28
  ## Usage
10
29
 
package/out/index.js CHANGED
@@ -4123,11 +4123,18 @@ async function cloudInteropOverride(config) {
4123
4123
  const { url, findIntentTimeout = DEFAULT_TIMEOUT, logLevel = 'warn', logger = new Logger(logLevel), ...settings } = config;
4124
4124
  const client = new CloudInteropAPI({ url });
4125
4125
  // create closure to use computed settings above in the CloudInteropOverride::reconnect function below
4126
- const initConnect = async (connectParams = settings) => client.connect({
4127
- ...connectParams,
4128
- // shim our logSink onto cloud-api logger func
4129
- logger: (level, msg) => logger.log(level, msg)
4130
- });
4126
+ const initConnect = async (connectParams = settings) => {
4127
+ client.connect({
4128
+ ...connectParams,
4129
+ // shim our logSink onto cloud-api logger func
4130
+ logger: (level, msg) => logger.log(level, msg)
4131
+ });
4132
+ };
4133
+ const isLocalTargetSession = (targetAppId) => {
4134
+ const target = typeof targetAppId === 'string' ? targetAppId : targetAppId.appId;
4135
+ const targetSessionId = client.parseSessionId(target);
4136
+ return !targetSessionId || targetSessionId === client.sessionDetails?.sessionId;
4137
+ };
4131
4138
  try {
4132
4139
  await initConnect();
4133
4140
  }
@@ -4177,9 +4184,10 @@ async function cloudInteropOverride(config) {
4177
4184
  client.addEventListener('raise-intent', async ({ initiatingSessionId, raiseOptions }) => {
4178
4185
  logger.log('debug', `Received raise-intent request from ${initiatingSessionId}`);
4179
4186
  if ('metadata' in raiseOptions.context && raiseOptions.context.metadata.target) {
4180
- const { target } = raiseOptions.context.metadata;
4181
- // parse the actual app id from the cloud-app-id string
4182
- raiseOptions.context.metadata.target = client.parseAppId(target);
4187
+ raiseOptions.context.metadata.target = {
4188
+ ...raiseOptions.context.metadata.target,
4189
+ appId: client.parseAppId(raiseOptions.context.metadata.target)
4190
+ };
4183
4191
  }
4184
4192
  let result;
4185
4193
  try {
@@ -4189,15 +4197,15 @@ async function cloudInteropOverride(config) {
4189
4197
  const intentResult = await intentResolution.getResult();
4190
4198
  const { intent, source, version } = intentResolution;
4191
4199
  result = { intent, source, version };
4200
+ logger.log('debug', `Intent result: ${JSON.stringify(intentResult, null, 2)}`);
4192
4201
  if (isContext(intentResult)) {
4193
4202
  // currently only supporting Contexts as results
4194
4203
  result.getResult = intentResult;
4195
4204
  }
4196
4205
  }
4197
4206
  catch (e) {
4198
- logger.log('warn', `Error in raise-intent, forwarding error response to the cloud: ${e}`);
4199
- // TODO: maybe set the message as a separate key to carry-forward the actual fdc3 error string
4200
- result = { error: `Error raising intent: ${e}` };
4207
+ logger.log('warn', `Error in raise-intent, forwarding error response to the cloud: ${e}`, e.stack);
4208
+ result = { error: e.message };
4201
4209
  }
4202
4210
  logger.log('debug', `Sending intent result to ${initiatingSessionId}:\n${JSON.stringify(result, null, 2)}`);
4203
4211
  await this.#maybeReconnect();
@@ -4207,48 +4215,64 @@ async function cloudInteropOverride(config) {
4207
4215
  async handleInfoForIntent(options, clientIdentity) {
4208
4216
  logger.log('debug', `in handleInfoForIntent`, options);
4209
4217
  const { name, context, metadata } = options;
4210
- return this.#handleInfoForIntent(name, context, metadata?.resultType);
4218
+ const intentsPromise = super.handleInfoForIntent(options, clientIdentity);
4219
+ return this.#handleInfoForIntent(intentsPromise, name, context, metadata?.resultType);
4211
4220
  }
4212
4221
  async handleInfoForIntentsByContext(context, clientIdentity) {
4213
4222
  logger.log('debug', `in handleInfoForIntentsByContext`, context);
4214
- return ('type' in context
4215
- ? this.#handleInfoForIntent(undefined, context, undefined)
4216
- : this.#handleInfoForIntent(undefined, context.context, context.metadata?.resultType));
4223
+ const intentsPromise = super.handleInfoForIntentsByContext(context, clientIdentity);
4224
+ return 'type' in context
4225
+ ? this.#handleInfoForIntent(intentsPromise, undefined, context, undefined)
4226
+ : this.#handleInfoForIntent(intentsPromise, undefined, context.context, context.metadata?.resultType);
4217
4227
  }
4218
4228
  async handleFiredIntent(intent, clientIdentity) {
4219
4229
  logger.log('debug', `in handleFiredIntent`, intent);
4220
- return this.#handleFiredIntent(intent);
4230
+ return this.#handleFiredIntent(clientIdentity, intent);
4221
4231
  }
4222
4232
  async handleFiredIntentForContext(contextForIntent, clientIdentity) {
4223
4233
  logger.log('debug', `in handleFiredIntentForContext`, contextForIntent);
4224
- return this.#handleFiredIntent(undefined, contextForIntent);
4234
+ return this.#handleFiredIntent(clientIdentity, contextForIntent);
4225
4235
  }
4226
- async #handleInfoForIntent(intent, context, resultType) {
4236
+ async #handleInfoForIntent(
4237
+ /**
4238
+ * Promise to execute in parallel with intent discovery
4239
+ */
4240
+ localIntentsPromise,
4241
+ /**
4242
+ * if present -> findIntent
4243
+ */
4244
+ intent,
4245
+ /**
4246
+ * if present -> findIntentsByContext
4247
+ */
4248
+ context, resultType) {
4227
4249
  logger.log('debug', `in #handleInfoForIntent`, { intent, context, resultType });
4228
- await this.#maybeReconnect();
4229
4250
  if (!intent && !context) {
4230
4251
  throw new Error('[CloudInteropOverride] #handleInfoForIntent requires intent or context');
4231
4252
  }
4232
4253
  if (context?.metadata?.target) {
4233
- const targetSessionId = client.parseSessionId(context?.metadata?.target);
4234
- if (targetSessionId && targetSessionId === client.sessionDetails?.sessionId) {
4235
- throw new Error('[CloudInteropOverride] handleInfoForIntent cannot be called on the same session');
4254
+ // safeguard MQ traffic by short circuiting here
4255
+ // if session id not parsable, it's a local intent, also if session id
4256
+ // is parsed and matches the current session, treat it as a local findIntent action
4257
+ if (isLocalTargetSession(context.metadata.target)) {
4258
+ logger.log('debug', `#handleInfoForIntent received an appId for the same session, returning local intent results only`);
4259
+ return localIntentsPromise;
4236
4260
  }
4237
4261
  }
4238
- const appIntentsMap = new Map();
4262
+ await this.#maybeReconnect();
4263
+ const appIntentMap = new Map();
4264
+ const upsertToMap = ({ intent, apps }) => {
4265
+ // merge all apps for the same intent
4266
+ const existingApps = appIntentMap.get(intent.name) ?? [];
4267
+ appIntentMap.set(intent.name, [...existingApps, ...apps]);
4268
+ };
4239
4269
  let aggregateResolve;
4240
4270
  const aggregatePromise = new Promise((resolve) => {
4241
4271
  aggregateResolve = resolve;
4242
4272
  });
4243
4273
  client.once('aggregate-intent-details', ({ responses }) => {
4244
4274
  logger.log('debug', `Received aggregate intent details, length: ${responses.length}`);
4245
- responses.forEach(({ intents }) => {
4246
- intents.forEach((intent) => {
4247
- const appIntentList = appIntentsMap.get(intent.intent.name) || [];
4248
- appIntentList.push(intent);
4249
- appIntentsMap.set(intent.intent.name, appIntentList);
4250
- });
4251
- });
4275
+ responses.forEach(({ intents }) => intents.forEach(upsertToMap));
4252
4276
  aggregateResolve();
4253
4277
  });
4254
4278
  const options = {
@@ -4261,25 +4285,35 @@ async function cloudInteropOverride(config) {
4261
4285
  await client.startIntentDiscovery(options);
4262
4286
  await aggregatePromise;
4263
4287
  logger.log('debug', 'Intent discovery completed');
4264
- const appIntents = intent
4265
- ? appIntentsMap.get(intent) || []
4266
- : Array.from(appIntentsMap.entries()).map(([intent, appIntents]) => ({
4267
- intent: { name: intent, displayName: intent },
4268
- apps: appIntents.flatMap(({ apps }) => apps)
4269
- }));
4270
- return appIntents;
4288
+ const localAppIntents = (await localIntentsPromise
4289
+ .then((intents) => (Array.isArray(intents) ? intents : [intents]))
4290
+ .catch(() => []));
4291
+ localAppIntents.forEach(upsertToMap);
4292
+ const appIntents = Array.from(appIntentMap.entries()).map(([intent, apps]) => ({
4293
+ intent: { name: intent, displayName: intent },
4294
+ apps
4295
+ }));
4296
+ if (appIntents.length === 0) {
4297
+ throw new Error('NoAppsFound');
4298
+ }
4299
+ return (intent ? appIntents[0] : appIntents);
4271
4300
  }
4272
- async #handleFiredIntent(intent, context) {
4273
- logger.log('debug', `in #handleFiredIntent`, { intent, context });
4301
+ async #handleFiredIntent(clientIdentity, input) {
4302
+ logger.log('debug', `in #handleFiredIntent`, { input });
4274
4303
  await this.#maybeReconnect();
4275
- const targetApp = intent?.metadata?.target ?? context?.metadata?.target;
4304
+ const targetApp = input?.metadata?.target;
4276
4305
  if (!targetApp) {
4277
4306
  throw new Error(`[CloudInteropOverride] handleFiredIntent requires intent.metadata.target or context.metadata.target to be an appId`);
4278
4307
  }
4308
+ const isIntent = (input) => 'name' in input;
4279
4309
  // safeguard MQ traffic by short circuiting here
4280
- const targetSessionId = client.parseSessionId(targetApp);
4281
- if (targetSessionId === client.sessionDetails?.sessionId) {
4282
- throw new Error('[CloudInteropOverride] handleFiredIntent cannot be called on the same session');
4310
+ // if session id not parsable, it's a local intent, also if session id
4311
+ // is parsed and matches the current session, treat it as a local intent
4312
+ if (isLocalTargetSession(targetApp)) {
4313
+ logger.log('debug', `#handleFiredIntent received an appId for the same session, firing intent locally`);
4314
+ return (isIntent(input)
4315
+ ? super.handleFiredIntent(input, clientIdentity)
4316
+ : super.handleFiredIntentForContext(input, clientIdentity));
4283
4317
  }
4284
4318
  let intentResolve;
4285
4319
  const intentResultPromise = new Promise((resolve) => {
@@ -4289,25 +4323,41 @@ async function cloudInteropOverride(config) {
4289
4323
  logger.log('debug', `Received intent-result`, JSON.stringify({ source, result }, null, 2));
4290
4324
  intentResolve(result);
4291
4325
  });
4292
- const raiseOptions = intent
4293
- ? { type: 'raise-intent', intent: intent.name, context: intent.context }
4294
- : { type: 'raise-intent-for-context', context: context };
4326
+ const raiseOptions = isIntent(input)
4327
+ ? { type: 'raise-intent', intent: input.name, context: input.context }
4328
+ : { type: 'raise-intent-for-context', context: input };
4295
4329
  await client.raiseIntent({ appId: targetApp, raiseOptions });
4296
4330
  // TODO: raise-intent timeout via promise.race?
4297
4331
  const intentResult = await intentResultPromise;
4332
+ const publishLocalResult = async (result) => {
4333
+ // NOTE: since we aren't changing getIntentResolution in the fdc3 impl code,
4334
+ // we "fake" a local intent result to ensure getResult resolves correctly, see:
4335
+ // js-adapter/src/api/interop/fdc3/utils.ts::getIntentResolution::fin.InterApplicationBus.subscribe
4336
+ const intentResolutionResultId = input.metadata?.intentResolutionResultId;
4337
+ // skip for web/external connections
4338
+ if (!this.fin.me.isOpenFin) {
4339
+ return;
4340
+ }
4341
+ if (intentResolutionResultId) {
4342
+ this.fin.InterApplicationBus.publish(intentResolutionResultId, result).catch(() => {
4343
+ logger.log('warn', `#handleFiredIntent could not create an intent resolution for AppId: ${targetApp}, getResult call will not work.`, intentResult);
4344
+ });
4345
+ }
4346
+ };
4298
4347
  if ('error' in intentResult) {
4299
4348
  logger.log('warn', `#handleFiredIntent could not raise intent for AppId: ${targetApp}`);
4349
+ // matching { error: Boolean } type of js-adapter/src/api/interop/fdc3/utils.ts::IntentResultPromisePayload
4350
+ publishLocalResult({ error: true });
4300
4351
  // Presumably, the error is FDC3 compliant, so throw it as is
4301
- // TODO: use the message key instead of the entire error string
4302
4352
  throw new Error(intentResult.error);
4303
4353
  }
4304
4354
  const source = typeof intentResult.source === 'string' ? { appId: intentResult.source } : intentResult.source;
4355
+ const finalResult = { ...intentResult, source };
4356
+ publishLocalResult(finalResult.getResult);
4305
4357
  return {
4306
- intent: intentResult.intent,
4307
- version: intentResult.version,
4308
- source,
4358
+ ...finalResult,
4309
4359
  // Currently only supporting contexts as results
4310
- getResult: async () => intentResult.getResult
4360
+ getResult: async () => finalResult.getResult
4311
4361
  };
4312
4362
  }
4313
4363
  async setContextForGroup({ context }, contextGroupId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/cloud-interop",
3
- "version": "0.40.38",
3
+ "version": "0.40.39",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "files": [
@@ -20,7 +20,7 @@
20
20
  "author": "",
21
21
  "license": "SEE LICENSE IN LICENSE.md",
22
22
  "peerDependencies": {
23
- "@openfin/core": "40.103.4"
23
+ "@openfin/core": "40.103.5"
24
24
  },
25
25
  "dependencies": {
26
26
  "@openfin/cloud-interop-core-api": "0.0.1-alpha.a27677b"