@openfin/cloud-interop 0.40.35 → 0.40.37

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/out/index.js CHANGED
@@ -4055,22 +4055,88 @@ class CloudInteropAPI {
4055
4055
  };
4056
4056
  }
4057
4057
 
4058
+ /* eslint-disable no-console */
4059
+ var LogLevel;
4060
+ (function (LogLevel) {
4061
+ LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
4062
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
4063
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
4064
+ // TODO: eventually support NONE = 3
4065
+ })(LogLevel || (LogLevel = {}));
4066
+ const levelToStr = new Map([
4067
+ [LogLevel.DEBUG, 'debug'],
4068
+ [LogLevel.INFO, 'info'],
4069
+ [LogLevel.WARN, 'warn']
4070
+ ]);
4071
+ const strToLevel = new Map([
4072
+ ['debug', LogLevel.DEBUG],
4073
+ ['info', LogLevel.INFO],
4074
+ ['warn', LogLevel.WARN],
4075
+ // reduce 'error' and 'log' to warn/info
4076
+ ['error', LogLevel.WARN],
4077
+ ['log', LogLevel.INFO]
4078
+ ]);
4079
+ const getTimestamp = () => new Date().toISOString().replace('T', ' ').replace('Z', '');
4080
+ /**
4081
+ * This logger retrofits on top of the CloudAPILogger function signature so it must use CloudAPILogLevel inputs
4082
+ */
4083
+ class Logger {
4084
+ logLevel;
4085
+ // eslint-disable-next-line no-useless-constructor
4086
+ constructor(level = 'warn') {
4087
+ this.logLevel = strToLevel.get(level) ?? LogLevel.WARN;
4088
+ }
4089
+ log(logLevel, message, ...args) {
4090
+ const level = strToLevel.get(logLevel) ?? this.logLevel;
4091
+ if (this.logLevel <= level) {
4092
+ // always write to console.debug to avoid cluttering client logs
4093
+ // TODO(RUN-9370): Change this to console.debug after this runtime ticket is fixed
4094
+ console.log(`${getTimestamp()} | CloudInteropOverride [${levelToStr.get(level)}] ${message}`, ...args);
4095
+ }
4096
+ }
4097
+ }
4098
+
4099
+ const isContext = (chan) => {
4100
+ const c = chan;
4101
+ return c && typeof c === 'object' && 'type' in c && !('broadcast' in c);
4102
+ };
4103
+
4104
+ const DEFAULT_TIMEOUT = 3000;
4058
4105
  /**
4059
4106
  * Enhances InteropBroker with Cloud Interop functionality
4107
+ *
4108
+ * Ensure that CloudInteropOverride comes first in the list of composable overrides, for example:
4109
+ * ```
4110
+ * const CloudInteropOverride = await cloudInteropOverride(settings);
4111
+ * await fin.Platform.init({
4112
+ * ...,
4113
+ * interopOverride: [
4114
+ * CloudInteropOverride, // <--- must come first
4115
+ * (BaseProvider) => class MyCustomProvider extends BaseProvider { ... }
4116
+ * ]
4117
+ * });
4118
+ * ```
4119
+ *
4060
4120
  * @param {CloudInteropOverrideParams} config Configuration to connect to the Cloud Interop service
4061
4121
  */
4062
4122
  async function cloudInteropOverride(config) {
4063
- const { url, ...settings } = config;
4123
+ const { url, findIntentTimeout = DEFAULT_TIMEOUT, logLevel = 'warn', logger = new Logger(logLevel), ...settings } = config;
4064
4124
  const client = new CloudInteropAPI({ url });
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
+ });
4065
4131
  try {
4066
- await client.connect(settings);
4132
+ await initConnect();
4067
4133
  }
4068
4134
  catch (err) {
4069
4135
  // eslint-disable-next-line no-console
4070
- console.warn(`[CloudInteropOverride] Failed to connect to Cloud Interop Service. Call interopBroker.cloudReconnect() to attempt reconnection`, err);
4136
+ logger.log('warn', `Failed to connect to Cloud Interop Service. Call interopBroker.cloudReconnect() to attempt reconnection`, err);
4071
4137
  }
4072
- return (Base) => {
4073
- return class CloudInteropOverride extends Base {
4138
+ return (BaseProvider) => {
4139
+ return class CloudInteropOverride extends BaseProvider {
4074
4140
  contextListener;
4075
4141
  setContextFilter = (context) => true;
4076
4142
  constructor() {
@@ -4085,10 +4151,169 @@ async function cloudInteropOverride(config) {
4085
4151
  }
4086
4152
  };
4087
4153
  client.addEventListener('context', this.contextListener);
4154
+ client.addEventListener('report-intents', async ({ discoveryId, initiatingSessionId, findOptions }) => {
4155
+ logger.log('debug', `Received report-intents request from ${initiatingSessionId}`);
4156
+ let appIntents = [];
4157
+ try {
4158
+ if (findOptions.type === 'find-intent') {
4159
+ const intent = (await super.handleInfoForIntent({
4160
+ name: findOptions.intent,
4161
+ metadata: { resultType: findOptions.resultType },
4162
+ context: findOptions.context
4163
+ }, null));
4164
+ appIntents = [intent];
4165
+ }
4166
+ else {
4167
+ appIntents = (await super.handleInfoForIntentsByContext({ context: findOptions.context, metadata: { resultType: findOptions.resultType } }, null));
4168
+ }
4169
+ }
4170
+ catch (e) {
4171
+ logger.log('warn', `Error in report-intents listener, proceeding with an empty response: ${e}`);
4172
+ }
4173
+ logger.log('debug', `Reporting intents :\n${JSON.stringify(appIntents, null, 2)}`);
4174
+ await this.#maybeReconnect();
4175
+ await client.reportAppIntents(discoveryId, appIntents);
4176
+ });
4177
+ client.addEventListener('raise-intent', async ({ initiatingSessionId, raiseOptions }) => {
4178
+ logger.log('debug', `Received raise-intent request from ${initiatingSessionId}`);
4179
+ 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);
4183
+ }
4184
+ let result;
4185
+ try {
4186
+ const intentResolution = (raiseOptions.type === 'raise-intent'
4187
+ ? await super.handleFiredIntent({ name: raiseOptions.intent, context: raiseOptions.context }, null)
4188
+ : await super.handleFiredIntentForContext(raiseOptions.context, null));
4189
+ const intentResult = await intentResolution.getResult();
4190
+ const { intent, source, version } = intentResolution;
4191
+ result = { intent, source, version };
4192
+ if (isContext(intentResult)) {
4193
+ // currently only supporting Contexts as results
4194
+ result.getResult = intentResult;
4195
+ }
4196
+ }
4197
+ 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}` };
4201
+ }
4202
+ logger.log('debug', `Sending intent result to ${initiatingSessionId}:\n${JSON.stringify(result, null, 2)}`);
4203
+ await this.#maybeReconnect();
4204
+ await client.sendIntentResult(initiatingSessionId, result);
4205
+ });
4206
+ }
4207
+ async handleInfoForIntent(options, clientIdentity) {
4208
+ logger.log('debug', `in handleInfoForIntent`, options);
4209
+ const { name, context, metadata } = options;
4210
+ return this.#handleInfoForIntent(name, context, metadata?.resultType);
4211
+ }
4212
+ async handleInfoForIntentsByContext(context, clientIdentity) {
4213
+ 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));
4217
+ }
4218
+ async handleFiredIntent(intent, clientIdentity) {
4219
+ logger.log('debug', `in handleFiredIntent`, intent);
4220
+ return this.#handleFiredIntent(intent);
4221
+ }
4222
+ async handleFiredIntentForContext(contextForIntent, clientIdentity) {
4223
+ logger.log('debug', `in handleFiredIntentForContext`, contextForIntent);
4224
+ return this.#handleFiredIntent(undefined, contextForIntent);
4225
+ }
4226
+ async #handleInfoForIntent(intent, context, resultType) {
4227
+ logger.log('debug', `in #handleInfoForIntent`, { intent, context, resultType });
4228
+ await this.#maybeReconnect();
4229
+ if (!intent && !context) {
4230
+ throw new Error('[CloudInteropOverride] #handleInfoForIntent requires intent or context');
4231
+ }
4232
+ 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');
4236
+ }
4237
+ }
4238
+ const appIntentsMap = new Map();
4239
+ let aggregateResolve;
4240
+ const aggregatePromise = new Promise((resolve) => {
4241
+ aggregateResolve = resolve;
4242
+ });
4243
+ client.once('aggregate-intent-details', ({ responses }) => {
4244
+ 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
+ });
4252
+ aggregateResolve();
4253
+ });
4254
+ const options = {
4255
+ timeout: findIntentTimeout,
4256
+ findOptions: intent
4257
+ ? { type: 'find-intent', intent, context, resultType }
4258
+ : { type: 'find-intents-by-context', context: context, resultType }
4259
+ };
4260
+ logger.log('debug', 'Starting intent discovery');
4261
+ await client.startIntentDiscovery(options);
4262
+ await aggregatePromise;
4263
+ 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;
4271
+ }
4272
+ async #handleFiredIntent(intent, context) {
4273
+ logger.log('debug', `in #handleFiredIntent`, { intent, context });
4274
+ await this.#maybeReconnect();
4275
+ const targetApp = intent?.metadata?.target ?? context?.metadata?.target;
4276
+ if (!targetApp) {
4277
+ throw new Error(`[CloudInteropOverride] handleFiredIntent requires intent.metadata.target or context.metadata.target to be an appId`);
4278
+ }
4279
+ // 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');
4283
+ }
4284
+ let intentResolve;
4285
+ const intentResultPromise = new Promise((resolve) => {
4286
+ intentResolve = resolve;
4287
+ });
4288
+ client.once('intent-result', ({ result, source }) => {
4289
+ logger.log('debug', `Received intent-result`, JSON.stringify({ source, result }, null, 2));
4290
+ intentResolve(result);
4291
+ });
4292
+ const raiseOptions = intent
4293
+ ? { type: 'raise-intent', intent: intent.name, context: intent.context }
4294
+ : { type: 'raise-intent-for-context', context: context };
4295
+ await client.raiseIntent({ appId: targetApp, raiseOptions });
4296
+ // TODO: raise-intent timeout via promise.race?
4297
+ const intentResult = await intentResultPromise;
4298
+ if ('error' in intentResult) {
4299
+ logger.log('warn', `#handleFiredIntent could not raise intent for AppId: ${targetApp}`);
4300
+ // Presumably, the error is FDC3 compliant, so throw it as is
4301
+ // TODO: use the message key instead of the entire error string
4302
+ throw new Error(intentResult.error);
4303
+ }
4304
+ const source = typeof intentResult.source === 'string' ? { appId: intentResult.source } : intentResult.source;
4305
+ return {
4306
+ intent: intentResult.intent,
4307
+ version: intentResult.version,
4308
+ source,
4309
+ // Currently only supporting contexts as results
4310
+ getResult: async () => intentResult.getResult
4311
+ };
4088
4312
  }
4089
4313
  async setContextForGroup({ context }, contextGroupId) {
4314
+ // TODO: is this right? should we attempt a reconnect instead of check connected state?
4090
4315
  if (this.cloudConnectionState === 'connected' && this.setContextFilter(context)) {
4091
- client.setContext(contextGroupId, context);
4316
+ await client.setContext(contextGroupId, context);
4092
4317
  }
4093
4318
  super.setContextForGroup({ context }, contextGroupId);
4094
4319
  }
@@ -4105,10 +4330,10 @@ async function cloudInteropOverride(config) {
4105
4330
  if (this.cloudConnectionState === 'connected') {
4106
4331
  await client.disconnect();
4107
4332
  }
4108
- await client.connect(settings || config);
4333
+ await initConnect(settings);
4109
4334
  }
4110
4335
  catch (err) {
4111
- console.warn(`[CloudInteropOverride] Failed reconnection to Cloud Interop Service.`, err);
4336
+ logger.log('warn', `Failed reconnection to Cloud Interop Service.`, err);
4112
4337
  }
4113
4338
  }
4114
4339
  /**
@@ -4140,6 +4365,12 @@ async function cloudInteropOverride(config) {
4140
4365
  }
4141
4366
  return 'disconnected';
4142
4367
  }
4368
+ #maybeReconnect = async () => {
4369
+ // TODO: Remove this helper when SAAS-1588 is solved
4370
+ if (this.cloudConnectionState !== 'connected') {
4371
+ await this.cloudReconnect();
4372
+ }
4373
+ };
4143
4374
  };
4144
4375
  };
4145
4376
  }
@@ -0,0 +1,17 @@
1
+ import { LogLevel as CloudAPILogLevel } from '@openfin/cloud-interop-core-api';
2
+ export declare const enum LogLevel {
3
+ DEBUG = 0,
4
+ INFO = 1,
5
+ WARN = 2
6
+ }
7
+ export interface LogSink {
8
+ log: (logLevel: CloudAPILogLevel, message: string, ...args: unknown[]) => void;
9
+ }
10
+ /**
11
+ * This logger retrofits on top of the CloudAPILogger function signature so it must use CloudAPILogLevel inputs
12
+ */
13
+ export declare class Logger implements LogSink {
14
+ private readonly logLevel;
15
+ constructor(level?: CloudAPILogLevel);
16
+ log(logLevel: CloudAPILogLevel, message: string, ...args: unknown[]): void;
17
+ }
package/out/override.d.ts CHANGED
@@ -1,11 +1,43 @@
1
- import { ConnectParameters } from '@openfin/cloud-interop-core-api';
1
+ import { LogLevel as CloudAPILogLevel, ConnectParameters } from '@openfin/cloud-interop-core-api';
2
2
  import type OpenFin from '@openfin/core';
3
- export type CloudInteropOverrideParams = ConnectParameters & {
3
+ import { LogSink } from './logger';
4
+ type ConnectParametersNoLogger = Omit<ConnectParameters, 'logger'>;
5
+ export type CloudInteropOverrideParams = ConnectParametersNoLogger & {
6
+ /**
7
+ * URL of the Cloud Interop service
8
+ */
4
9
  url: string;
10
+ /**
11
+ * How long to wait for connected clients to respond, defaults to 3 seconds.
12
+ * Do not make this number too high, as it will block findIntent/findIntentsByContext calls
13
+ */
14
+ findIntentTimeout?: number;
15
+ /**
16
+ * If provided will defer to this logger, otherwise logs to console.debug, reduced by LogLevel
17
+ */
18
+ logger?: LogSink;
19
+ /**
20
+ * The level which cloud-interop override will log. Defaults to 'warn'
21
+ */
22
+ logLevel?: CloudAPILogLevel;
5
23
  };
6
24
  export type CloudInteropConnectionStates = 'connected' | 'reconnecting' | 'disconnected';
7
25
  /**
8
26
  * Enhances InteropBroker with Cloud Interop functionality
27
+ *
28
+ * Ensure that CloudInteropOverride comes first in the list of composable overrides, for example:
29
+ * ```
30
+ * const CloudInteropOverride = await cloudInteropOverride(settings);
31
+ * await fin.Platform.init({
32
+ * ...,
33
+ * interopOverride: [
34
+ * CloudInteropOverride, // <--- must come first
35
+ * (BaseProvider) => class MyCustomProvider extends BaseProvider { ... }
36
+ * ]
37
+ * });
38
+ * ```
39
+ *
9
40
  * @param {CloudInteropOverrideParams} config Configuration to connect to the Cloud Interop service
10
41
  */
11
42
  export declare function cloudInteropOverride(config: CloudInteropOverrideParams): Promise<OpenFin.ConstructorOverride<OpenFin.InteropBroker>>;
43
+ export {};
package/out/utils.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { v2_0 as FDC3v2 } from '@openfin/fdc3';
2
+ export declare const isContext: (chan: unknown) => chan is FDC3v2.Context;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/cloud-interop",
3
- "version": "0.40.35",
3
+ "version": "0.40.37",
4
4
  "description": "",
5
5
  "private": false,
6
6
  "files": [
@@ -11,6 +11,7 @@
11
11
  "scripts": {
12
12
  "prebuild": "rimraf out",
13
13
  "build": "rollup -c",
14
+ "watch": "rollup -c --watch",
14
15
  "ci:prepublish": "of-npm prepublish",
15
16
  "ci:postpublish": "of-npm postpublish",
16
17
  "ci:publish": "npm publish",
@@ -19,9 +20,9 @@
19
20
  "author": "",
20
21
  "license": "SEE LICENSE IN LICENSE.md",
21
22
  "peerDependencies": {
22
- "@openfin/core": "40.102.2"
23
+ "@openfin/core": "40.103.1"
23
24
  },
24
25
  "dependencies": {
25
- "@openfin/cloud-interop-core-api": "^0.0.1-alpha.fba3468"
26
+ "@openfin/cloud-interop-core-api": "0.0.1-alpha.a27677b"
26
27
  }
27
28
  }