diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e9e3bbe..7cce65670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added conformance tests into the FDC3 API documentation in the current version and backported into 2.0 and 2.1. Removed outdated 1.2 conformance tests (which are preserved in the older 2.0 and 2.1 versions). ([#1417](https://github.com/finos/FDC3/pull/1417)) * Added separate `fdc3-commonjs` module for compatibility with older projects that use CommonJS. ([#1452](https://github.com/finos/FDC3/pull/1452)) * Added testing policy to [Contributing](CONTRIBUTING) page to address ([810](https://github.com/finos/FDC3/issues/810)) +* Added the ability to control logging to the JS console from getAgent() and the DesktopAgentProxy via arguments to getAgent(). ([#1495](https://github.com/finos/FDC3/pull/1495)) + ### Changed diff --git a/packages/fdc3-agent-proxy/src/DesktopAgentProxy.ts b/packages/fdc3-agent-proxy/src/DesktopAgentProxy.ts index 1a8c80838..a3d903a7c 100644 --- a/packages/fdc3-agent-proxy/src/DesktopAgentProxy.ts +++ b/packages/fdc3-agent-proxy/src/DesktopAgentProxy.ts @@ -16,6 +16,12 @@ import { IntentSupport } from './intents/IntentSupport'; import { Connectable, Channel } from '@finos/fdc3-standard'; import { Context } from '@finos/fdc3-context'; import { HeartbeatSupport } from './heartbeat/HeartbeatSupport'; +import { Logger } from './util/Logger'; + +export type DesktopAgentProxyLogSettings = { + heartbeat: boolean; + debug: boolean; +}; /** * This splits out the functionality of the desktop agent into @@ -33,13 +39,16 @@ export class DesktopAgentProxy implements DesktopAgent, Connectable { channels: ChannelSupport, intents: IntentSupport, apps: AppSupport, - connectables: Connectable[] + connectables: Connectable[], + logging: DesktopAgentProxyLogSettings ) { this.heartbeat = heartbeat; this.intents = intents; this.channels = channels; this.apps = apps; this.connectables = connectables; + Logger.enableHeartbeatLogs(logging.heartbeat); + Logger.enableDebugLogs(logging.debug); } addEventListener(type: FDC3EventTypes | null, handler: EventHandler): Promise { @@ -47,7 +56,7 @@ export class DesktopAgentProxy implements DesktopAgent, Connectable { case 'userChannelChanged': return this.channels.addChannelChangedEventHandler(handler); default: - console.warn(`Tried to add a listener for an unknown event type: ${type}`); + Logger.warn(`Tried to add a listener for an unknown event type: ${type}`); return Promise.reject(new Error('UnknownEventType')); } } diff --git a/packages/fdc3-agent-proxy/src/apps/DefaultAppSupport.ts b/packages/fdc3-agent-proxy/src/apps/DefaultAppSupport.ts index a40758b7a..5777c2e58 100644 --- a/packages/fdc3-agent-proxy/src/apps/DefaultAppSupport.ts +++ b/packages/fdc3-agent-proxy/src/apps/DefaultAppSupport.ts @@ -12,7 +12,9 @@ import { OpenRequest, OpenResponse, } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; -import { throwIfUndefined } from '../util'; +import { throwIfUndefined } from '../util/throwIfUndefined'; +import { Logger } from '../util/Logger'; + export class DefaultAppSupport implements AppSupport { readonly messaging: Messaging; @@ -96,7 +98,7 @@ export class DefaultAppSupport implements AppSupport { return response.payload.implementationMetadata; } else { //This will only happen if the DA implementation returns an invalid message with a missing implementationMetadata property - console.error('Invalid response from Desktop Agent to open!', response); + Logger.error('Invalid response from Desktop Agent to open!', response); const unknownImpl: ImplementationMetadata = { fdc3Version: 'unknown', provider: 'unknown', diff --git a/packages/fdc3-agent-proxy/src/channels/DefaultChannelSupport.ts b/packages/fdc3-agent-proxy/src/channels/DefaultChannelSupport.ts index 1d6542672..83bfb4f4e 100644 --- a/packages/fdc3-agent-proxy/src/channels/DefaultChannelSupport.ts +++ b/packages/fdc3-agent-proxy/src/channels/DefaultChannelSupport.ts @@ -28,7 +28,8 @@ import { JoinUserChannelResponse, JoinUserChannelRequest, } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; -import { throwIfUndefined } from '../util'; +import { throwIfUndefined } from '../util/throwIfUndefined'; +import { Logger } from '../util/Logger'; export class DefaultChannelSupport implements ChannelSupport { readonly messaging: Messaging; @@ -40,7 +41,7 @@ export class DefaultChannelSupport implements ChannelSupport { this.messaging = messaging; this.channelSelector = channelSelector; this.channelSelector.setChannelChangeCallback((channelId: string | null) => { - console.debug('Channel selector reports channel changed: ' + channelId); + Logger.debug('Channel selector reports channel changed: ', channelId); if (channelId == null) { this.leaveUserChannel(); } else { @@ -49,6 +50,7 @@ export class DefaultChannelSupport implements ChannelSupport { }); this.addChannelChangedEventHandler(e => { + Logger.debug('Desktop Agent reports channel changed: ', e.details.newChannelId); this.channelSelector.updateChannel(e.details.newChannelId, this.userChannels); }); } @@ -177,7 +179,6 @@ export class DefaultChannelSupport implements ChannelSupport { } async addContextListener(handler: ContextHandler, type: string | null): Promise { - //TODO: Figure out a better solution than inlining this class. /** Utility class used to wrap the DefaultContextListener and ensure it gets removed * when its unsubscribe function is called. */ diff --git a/packages/fdc3-agent-proxy/src/index.ts b/packages/fdc3-agent-proxy/src/index.ts index 19955f2e2..df1207355 100644 --- a/packages/fdc3-agent-proxy/src/index.ts +++ b/packages/fdc3-agent-proxy/src/index.ts @@ -11,6 +11,7 @@ import { DefaultAppSupport } from './apps/DefaultAppSupport'; import { AppSupport } from './apps/AppSupport'; import { DefaultHeartbeatSupport } from './heartbeat/DefaultHeartbeatSupport'; import { Connectable } from '@finos/fdc3-standard'; +import { AbstractFDC3Logger } from './util/AbstractFDC3Logger'; export { type Messaging, @@ -26,4 +27,5 @@ export { DefaultHeartbeatSupport, RegisterableListener, Connectable, + AbstractFDC3Logger, }; diff --git a/packages/fdc3-agent-proxy/src/intents/DefaultIntentSupport.ts b/packages/fdc3-agent-proxy/src/intents/DefaultIntentSupport.ts index 60ee02be8..94cf5ca54 100644 --- a/packages/fdc3-agent-proxy/src/intents/DefaultIntentSupport.ts +++ b/packages/fdc3-agent-proxy/src/intents/DefaultIntentSupport.ts @@ -27,7 +27,7 @@ import { RaiseIntentResponse, RaiseIntentResultResponse, } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; -import { throwIfUndefined } from '../util'; +import { throwIfUndefined } from '../util/throwIfUndefined'; const convertIntentResult = async ( { payload }: RaiseIntentResultResponse, diff --git a/packages/fdc3-agent-proxy/src/listeners/AbstractListener.ts b/packages/fdc3-agent-proxy/src/listeners/AbstractListener.ts index 2d68c91c0..9bdcad9a2 100644 --- a/packages/fdc3-agent-proxy/src/listeners/AbstractListener.ts +++ b/packages/fdc3-agent-proxy/src/listeners/AbstractListener.ts @@ -19,7 +19,7 @@ import { } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; import { Messaging } from '../Messaging'; import { RegisterableListener } from './RegisterableListener'; -import { throwIfUndefined } from '../util'; +import { throwIfUndefined } from '../util/throwIfUndefined'; import { ChannelError } from '@finos/fdc3-standard'; type SubscriptionRequest = diff --git a/packages/fdc3-agent-proxy/src/listeners/HeartbeatListener.ts b/packages/fdc3-agent-proxy/src/listeners/HeartbeatListener.ts index 28f145339..250fb9919 100644 --- a/packages/fdc3-agent-proxy/src/listeners/HeartbeatListener.ts +++ b/packages/fdc3-agent-proxy/src/listeners/HeartbeatListener.ts @@ -5,6 +5,7 @@ import { } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; import { Messaging } from '../Messaging'; import { RegisterableListener } from './RegisterableListener'; +import { Logger } from '../util/Logger'; export class HeartbeatListener implements RegisterableListener { readonly id: string; @@ -20,6 +21,7 @@ export class HeartbeatListener implements RegisterableListener { } action(_m: AgentEventMessage): void { + Logger.heartbeatLog('Responding to heartbeat request', _m); const request: HeartbeatAcknowledgementRequest = { type: 'heartbeatAcknowledgementRequest', meta: { diff --git a/packages/fdc3-agent-proxy/src/listeners/PrivateChannelEventListener.ts b/packages/fdc3-agent-proxy/src/listeners/PrivateChannelEventListener.ts index 80fbfe272..77cb08710 100644 --- a/packages/fdc3-agent-proxy/src/listeners/PrivateChannelEventListener.ts +++ b/packages/fdc3-agent-proxy/src/listeners/PrivateChannelEventListener.ts @@ -10,6 +10,7 @@ import { PrivateChannelUnsubscribeEvent, } from '@finos/fdc3-standard'; import { BrowserTypes } from '@finos/fdc3-schema'; +import { Logger } from '../util/Logger'; const { isPrivateChannelOnAddContextListenerEvent, isPrivateChannelOnDisconnectEvent, @@ -116,7 +117,7 @@ export class PrivateChannelDisconnectEventListener extends AbstractPrivateChanne }; handler(event); } else { - console.error('PrivateChannelDisconnectEventListener was called for a different message type!', msg); + Logger.error('PrivateChannelDisconnectEventListener was called for a different message type!', msg); } }; @@ -134,7 +135,7 @@ export class PrivateChannelAddContextEventListener extends AbstractPrivateChanne }; handler(event); } else { - console.error('PrivateChannelAddContextEventListener was called for a different message type!', msg); + Logger.error('PrivateChannelAddContextEventListener was called for a different message type!', msg); } }; super(messaging, channelId, ['privateChannelOnAddContextListenerEvent'], 'addContextListener', wrappedHandler); @@ -151,7 +152,7 @@ export class PrivateChannelUnsubscribeEventListener extends AbstractPrivateChann }; handler(event); } else { - console.error('PrivateChannelUnsubscribeEventListener was called for a different message type!', msg); + Logger.error('PrivateChannelUnsubscribeEventListener was called for a different message type!', msg); } }; super(messaging, channelId, ['privateChannelOnUnsubscribeEvent'], 'unsubscribe', wrappedHandler); diff --git a/packages/fdc3-agent-proxy/src/messaging/AbstractMessaging.ts b/packages/fdc3-agent-proxy/src/messaging/AbstractMessaging.ts index b6733319e..7f7069660 100644 --- a/packages/fdc3-agent-proxy/src/messaging/AbstractMessaging.ts +++ b/packages/fdc3-agent-proxy/src/messaging/AbstractMessaging.ts @@ -6,6 +6,7 @@ import { AppRequestMessage, WebConnectionProtocol6Goodbye, } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; +import { Logger } from '../util/Logger'; export abstract class AbstractMessaging implements Messaging { private appIdentifier: AppIdentifier; @@ -30,6 +31,7 @@ export abstract class AbstractMessaging implements Messaging { id, filter: filter, action: m => { + Logger.debug('Received from DesktopAgent: ', m); done = true; this.unregister(id); if (timeout) { @@ -51,7 +53,7 @@ export abstract class AbstractMessaging implements Messaging { timeout = setTimeout(() => { this.unregister(id); if (!done) { - console.error( + Logger.error( `waitFor rejecting after ${this.getTimeoutMs()}ms at ${new Date().toISOString()} with ${timeoutErrorMessage}` ); reject(new Error(timeoutErrorMessage)); @@ -71,6 +73,7 @@ export abstract class AbstractMessaging implements Messaging { const prom = this.waitFor(m => { return m.type == expectedTypeName && m.meta.requestUuid == message.meta.requestUuid; }, errorMessage); + Logger.debug('Sending to DesktopAgent: ', message); this.post(message); const out: X = await prom; if (out?.payload?.error) { diff --git a/packages/fdc3-agent-proxy/src/util/AbstractFDC3Logger.ts b/packages/fdc3-agent-proxy/src/util/AbstractFDC3Logger.ts new file mode 100644 index 000000000..16a932d0e --- /dev/null +++ b/packages/fdc3-agent-proxy/src/util/AbstractFDC3Logger.ts @@ -0,0 +1,89 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +//check if color is supported in (node) console; +let noColor = true; +//else only occurs in a browser and can't be tested in node +/* istanbul ignore if */ +if (typeof process !== 'undefined') { + const argv = process.argv || /* istanbul ignore next */ []; + const env = process.env || /* istanbul ignore next */ {}; + noColor = + (!!env.NO_COLOR || argv.includes('--no-color')) && + !( + !!env.FORCE_COLOR || + argv.includes('--color') || + process.platform === 'win32' /* istanbul ignore next */ || + ((process.stdout || {}).isTTY && env.TERM !== 'dumb') || + /* istanbul ignore next */ !!env.CI + ); +} + +type ColorFn = (aString: string) => string; + +export abstract class AbstractFDC3Logger { + private static enableDebug: boolean = false; + private static enableLog: boolean = true; + + /** This should be overridden by sub-classes to change the prefix applied + * to log messages. */ + /* istanbul ignore next */ static get prefix(): string { + return ''; + } + + public static enableDebugLogs(enable: boolean) { + this.enableDebug = enable; + } + + public static enableLogs(enable: boolean) { + this.enableLog = enable; + } + + protected static debugColor(value: string): string { + return noColor ? /* istanbul ignore next */ value : '\x1b[30m\x1b[2m' + value + '\x1b[22m\x1b[39m'; + } + protected static logColor(value: string): string { + return noColor ? /* istanbul ignore next */ value : '\x1b[32m\x1b[2m' + value + '\x1b[22m\x1b[39m'; + } + protected static warnColor(value: string): string { + return noColor ? /* istanbul ignore next */ value : '\x1b[33m' + value + '\x1b[39m'; + } + protected static errorColor(value: string): string { + return noColor ? /* istanbul ignore next */ value : '\x1b[31m' + value + '\x1b[39m'; + } + + public static debug(...params: any[]) { + if (this.enableDebug) { + console.debug(...this.prefixAndColorize(this.prefix, params, this.debugColor)); + } + } + + public static log(...params: any[]) { + if (this.enableLog) { + console.log(...this.prefixAndColorize(this.prefix, params, this.logColor)); + } + } + + public static warn(...params: any[]) { + console.warn(...this.prefixAndColorize(this.prefix, params, this.warnColor)); + } + + public static error(...params: any[]) { + console.error(...this.prefixAndColorize(this.prefix, params, this.errorColor)); + } + + protected static prefixAndColorize = (prefix: string, params: any[], colorFn: ColorFn): string[] => { + const prefixed = [prefix, ...params]; + return prefixed.map(value => { + if (typeof value === 'string') { + //just color strings + return colorFn(value); + } else if (value && value.stack && value.message) { + //probably an error + return colorFn(value.stack); + } else { + //something else... lets hope it stringifies + return colorFn(JSON.stringify(value, null, 2)); + } + }); + }; +} diff --git a/packages/fdc3-agent-proxy/src/util/Logger.ts b/packages/fdc3-agent-proxy/src/util/Logger.ts new file mode 100644 index 000000000..62aaeef06 --- /dev/null +++ b/packages/fdc3-agent-proxy/src/util/Logger.ts @@ -0,0 +1,19 @@ +import { AbstractFDC3Logger } from './AbstractFDC3Logger'; + +export class Logger extends AbstractFDC3Logger { + static override get prefix(): string { + return 'FDC3 DesktopAgentProxy: '; + } + + private static enableHeartbeatLog: boolean = true; + + public static enableHeartbeatLogs(enable: boolean) { + this.enableHeartbeatLog = enable; + } + + public static heartbeatLog(...params: any[]) { + if (this.enableHeartbeatLog) { + console.debug(...this.prefixAndColorize(this.prefix, params, this.debugColor)); + } + } +} diff --git a/packages/fdc3-agent-proxy/src/util.ts b/packages/fdc3-agent-proxy/src/util/throwIfUndefined.ts similarity index 84% rename from packages/fdc3-agent-proxy/src/util.ts rename to packages/fdc3-agent-proxy/src/util/throwIfUndefined.ts index 80acf88d0..4bdd739a3 100644 --- a/packages/fdc3-agent-proxy/src/util.ts +++ b/packages/fdc3-agent-proxy/src/util/throwIfUndefined.ts @@ -1,5 +1,6 @@ import { AgentEventMessage, AgentResponseMessage } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; import { ChannelError, OpenError, ResolveError } from '@finos/fdc3-standard'; +import { Logger } from './Logger'; export type ErrorMessages = ChannelError | OpenError | ResolveError; @@ -14,7 +15,7 @@ export const throwIfUndefined = ( absentError: ErrorMessages ): void => { if (property === undefined) { - console.error(absentMessage, '\nDACP message that resulted in the undefined property: ', message); + Logger.error(absentMessage, '\nDACP message that resulted in the undefined property: ', message); throw new Error(absentError); } }; diff --git a/packages/fdc3-agent-proxy/test/features/utils.feature b/packages/fdc3-agent-proxy/test/features/utils.feature index 36ac80faa..6298470e3 100644 --- a/packages/fdc3-agent-proxy/test/features/utils.feature +++ b/packages/fdc3-agent-proxy/test/features/utils.feature @@ -3,4 +3,8 @@ Feature: Utility functions Scenario: throwIfUndefined is used to check properties When I call throwIfUndefined it throws if a specified property is not defined And I call throwIfUndefined it does NOT throw if a specified property IS defined + + Scenario: Logger utility + When All log functions are used with a message + When All log functions are used with an error \ No newline at end of file diff --git a/packages/fdc3-agent-proxy/test/step-definitions/channelSelector.steps.ts b/packages/fdc3-agent-proxy/test/step-definitions/channelSelector.steps.ts index 65fbb8592..5e8e8f19f 100644 --- a/packages/fdc3-agent-proxy/test/step-definitions/channelSelector.steps.ts +++ b/packages/fdc3-agent-proxy/test/step-definitions/channelSelector.steps.ts @@ -27,7 +27,7 @@ Given( const is = new DefaultIntentSupport(this.messaging, new SimpleIntentResolver(this)); const as = new DefaultAppSupport(this.messaging); - const da = new DesktopAgentProxy(hs, cs, is, as, [hs]); + const da = new DesktopAgentProxy(hs, cs, is, as, [hs], { debug: true, heartbeat: false }); await da.connect(); this.props[daField] = da; diff --git a/packages/fdc3-agent-proxy/test/step-definitions/generic.steps.ts b/packages/fdc3-agent-proxy/test/step-definitions/generic.steps.ts index 04850995c..99c4934c4 100644 --- a/packages/fdc3-agent-proxy/test/step-definitions/generic.steps.ts +++ b/packages/fdc3-agent-proxy/test/step-definitions/generic.steps.ts @@ -22,7 +22,7 @@ Given('A Desktop Agent in {string}', async function (this: CustomWorld, field: s const is = new DefaultIntentSupport(this.messaging, new SimpleIntentResolver(this)); const as = new DefaultAppSupport(this.messaging); - const da = new DesktopAgentProxy(hs, cs, is, as, [hs]); + const da = new DesktopAgentProxy(hs, cs, is, as, [hs], { debug: false, heartbeat: false }); await da.connect(); this.props[field] = da; diff --git a/packages/fdc3-agent-proxy/test/step-definitions/util.steps.ts b/packages/fdc3-agent-proxy/test/step-definitions/util.steps.ts index 095bbafa8..63fb6f0a9 100644 --- a/packages/fdc3-agent-proxy/test/step-definitions/util.steps.ts +++ b/packages/fdc3-agent-proxy/test/step-definitions/util.steps.ts @@ -1,9 +1,10 @@ import { When } from '@cucumber/cucumber'; import { CustomWorld } from '../world'; -import { throwIfUndefined } from '../../src/util'; +import { throwIfUndefined } from '../../src/util/throwIfUndefined'; import { AgentResponseMessage } from '@finos/fdc3-schema/dist/generated/api/BrowserTypes'; import { OpenError } from '@finos/fdc3-standard'; import expect from 'expect'; +import { Logger } from '../../src/util/Logger'; When('I call throwIfUndefined it throws if a specified property is not defined', async function (this: CustomWorld) { let thrown: Error | null = null; @@ -61,3 +62,27 @@ When( expect(thrown).toBeNull(); } ); + +const TEST_ERROR = 'Test error - This is expected on the console'; + +When('All log functions are used with a message', async function (this: CustomWorld) { + Logger.enableLogs(true); + Logger.enableDebugLogs(true); + Logger.enableHeartbeatLogs(true); + Logger.debug('Debug msg'); + Logger.heartbeatLog('Heartbeat debug msg'); + Logger.log('Log msg'); + Logger.warn('Warning msg'); + Logger.error('Error msg'); +}); + +When('All log functions are used with an error', async function (this: CustomWorld) { + Logger.enableLogs(true); + Logger.enableDebugLogs(true); + Logger.enableHeartbeatLogs(true); + Logger.debug('debug-level error: ', new Error(TEST_ERROR)); + Logger.heartbeatLog('heartbeat debug-level error: ', new Error(TEST_ERROR)); + Logger.log('log-level error: ', new Error(TEST_ERROR)); + Logger.warn('warn-level error: ', new Error(TEST_ERROR)); + Logger.error('error-level error: ', new Error(TEST_ERROR)); +}); diff --git a/packages/fdc3-get-agent/src/messaging/message-port.ts b/packages/fdc3-get-agent/src/messaging/message-port.ts index 438ef2df0..01f76120b 100644 --- a/packages/fdc3-get-agent/src/messaging/message-port.ts +++ b/packages/fdc3-get-agent/src/messaging/message-port.ts @@ -20,7 +20,11 @@ import { Logger } from '../util/Logger'; */ export async function createDesktopAgentAPI( cd: ConnectionDetails, - appIdentifier: AppIdentifier + appIdentifier: AppIdentifier, + logging: { + heartbeat: boolean; + debug: boolean; + } ): Promise { Logger.debug('message-port: Creating Desktop Agent...'); @@ -53,7 +57,7 @@ export async function createDesktopAgentAPI( const cs = new DefaultChannelSupport(messaging, channelSelector); const is = new DefaultIntentSupport(messaging, intentResolver); const as = new DefaultAppSupport(messaging); - const da = new DesktopAgentProxy(hs, cs, is, as, [hs, intentResolver, channelSelector]); + const da = new DesktopAgentProxy(hs, cs, is, as, [hs, intentResolver, channelSelector], logging); Logger.debug('message-port: Connecting components ...'); diff --git a/packages/fdc3-get-agent/src/strategies/FailoverHandler.ts b/packages/fdc3-get-agent/src/strategies/FailoverHandler.ts index 0f4c29e25..736e97e72 100644 --- a/packages/fdc3-get-agent/src/strategies/FailoverHandler.ts +++ b/packages/fdc3-get-agent/src/strategies/FailoverHandler.ts @@ -6,6 +6,7 @@ import { HelloHandler } from './HelloHandler'; import { IdentityValidationHandler } from './IdentityValidationHandler'; import { Logger } from '../util/Logger'; import { ConnectionDetails } from '../messaging/MessagePortMessaging'; +import { DesktopAgentProxyLogSettings } from '@finos/fdc3-agent-proxy/src/DesktopAgentProxy'; /** TypeGuard for a Desktop Agent */ function isDesktopAgent(da: WindowProxy | DesktopAgent): da is DesktopAgent { @@ -90,8 +91,15 @@ export class FailoverHandler { appId: idDetails.payload.appId, instanceId: idDetails.payload.instanceId, }; + + //prep log settings to pass on to the proxy + const logging: DesktopAgentProxyLogSettings = { + debug: this.options.logging?.proxyDebug ?? false, + heartbeat: this.options.logging?.heartbeat ?? false, + }; + const desktopAgentSelection: DesktopAgentSelection = { - agent: await createDesktopAgentAPI(connectionDetails, appIdentifier), + agent: await createDesktopAgentAPI(connectionDetails, appIdentifier, logging), details: { agentType: connectionDetails.agentType, agentUrl: connectionDetails.agentUrl ?? undefined, diff --git a/packages/fdc3-get-agent/src/strategies/PostMessageLoader.ts b/packages/fdc3-get-agent/src/strategies/PostMessageLoader.ts index 0b4163a5d..1605de22e 100644 --- a/packages/fdc3-get-agent/src/strategies/PostMessageLoader.ts +++ b/packages/fdc3-get-agent/src/strategies/PostMessageLoader.ts @@ -5,6 +5,7 @@ import { DesktopAgentSelection, Loader } from './Loader'; import { HelloHandler } from './HelloHandler'; import { IdentityValidationHandler } from './IdentityValidationHandler'; import { Logger } from '../util/Logger'; +import { DesktopAgentProxyLogSettings } from '@finos/fdc3-agent-proxy/src/DesktopAgentProxy'; /** * Recursive search for all possible parent frames (windows) that we may @@ -41,8 +42,15 @@ function _recursePossibleTargets(startWindow: Window, w: Window, found: Window[] */ export class PostMessageLoader implements Loader { name = 'PostMessageLoader'; + private logging: DesktopAgentProxyLogSettings; + + constructor(options: GetAgentParams, previousUrl?: string) { + //prep log settings to pass on to the proxy + this.logging = { + debug: options.logging?.proxyDebug ?? false, + heartbeat: options.logging?.heartbeat ?? false, + }; - constructor(previousUrl?: string) { this.previousUrl = previousUrl ?? null; } @@ -128,7 +136,7 @@ export class PostMessageLoader implements Loader { instanceId: idDetails.payload.instanceId, }; - createDesktopAgentAPI(connectionDetails, appIdentifier).then(da => { + createDesktopAgentAPI(connectionDetails, appIdentifier, this.logging).then(da => { const desktopAgentSelection: DesktopAgentSelection = { agent: da, details: { diff --git a/packages/fdc3-get-agent/src/strategies/getAgent.ts b/packages/fdc3-get-agent/src/strategies/getAgent.ts index f89d99871..5903ecf3b 100644 --- a/packages/fdc3-get-agent/src/strategies/getAgent.ts +++ b/packages/fdc3-get-agent/src/strategies/getAgent.ts @@ -34,6 +34,9 @@ export function clearAgentPromise() { } function initAgentPromise(options: GetAgentParams): Promise { + Logger.enableLogs(options.logging?.connection ?? true); + Logger.enableDebugLogs(options.logging?.connectionDebug ?? false); + Logger.log(`Initiating Desktop Agent discovery at ${new Date().toISOString()}`); let strategies: Loader[]; @@ -51,20 +54,20 @@ function initAgentPromise(options: GetAgentParams): Promise { break; case WebDesktopAgentType.ProxyUrl: //agentUrl will only be used by PostMessageLoader if not falsey - strategies = [new PostMessageLoader(persistedData.agentUrl)]; + strategies = [new PostMessageLoader(options, persistedData.agentUrl)]; break; case WebDesktopAgentType.ProxyParent: - strategies = [new PostMessageLoader()]; + strategies = [new PostMessageLoader(options)]; break; case WebDesktopAgentType.Failover: strategies = []; break; default: Logger.warn('Unexpected agentType value in SessionStorage, ignoring. Stored data:', persistedData); - strategies = [new DesktopAgentPreloadLoader(), new PostMessageLoader()]; + strategies = [new DesktopAgentPreloadLoader(), new PostMessageLoader(options)]; } } else { - strategies = [new DesktopAgentPreloadLoader(), new PostMessageLoader()]; + strategies = [new DesktopAgentPreloadLoader(), new PostMessageLoader(options)]; } const promises = strategies.map(s => diff --git a/packages/fdc3-get-agent/src/util/Logger.ts b/packages/fdc3-get-agent/src/util/Logger.ts index a207a99b2..990b2a484 100644 --- a/packages/fdc3-get-agent/src/util/Logger.ts +++ b/packages/fdc3-get-agent/src/util/Logger.ts @@ -1,61 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -const GET_AGENT_LOG_PREFIX = 'FDC3 getAgent: '; +import { AbstractFDC3Logger } from '@finos/fdc3-agent-proxy'; -//check if color is supported in console; -let noColor = true; -//else only occurs in a browser and can't be tested in node -/* istanbul ignore if */ -if (typeof process !== 'undefined') { - const argv = process.argv || /* istanbul ignore next */ []; - const env = process.env || /* istanbul ignore next */ {}; - noColor = - (!!env.NO_COLOR || argv.includes('--no-color')) && - !( - !!env.FORCE_COLOR || - argv.includes('--color') || - process.platform === 'win32' /* istanbul ignore next */ || - ((process.stdout || {}).isTTY && env.TERM !== 'dumb') || - /* istanbul ignore next */ !!env.CI - ); -} - -type ColorFn = (aString: string) => string; - -const debugColor: ColorFn = value => (noColor ? value : '\x1b[30m\x1b[2m' + value + '\x1b[22m\x1b[39m'); -const logColor: ColorFn = value => (noColor ? value : '\x1b[32m\x1b[2m' + value + '\x1b[22m\x1b[39m'); -const warnColor: ColorFn = value => (noColor ? value : '\x1b[33m' + value + '\x1b[39m'); -const errorColor: ColorFn = value => (noColor ? value : '\x1b[31m' + value + '\x1b[39m'); - -const prefixAndColorize = (params: any[], colorFn: ColorFn): string[] => { - const prefixed = [GET_AGENT_LOG_PREFIX, ...params]; - return prefixed.map(value => { - if (typeof value === 'string') { - //just color strings - return colorFn(value); - } else if (value && value.stack && value.message) { - //probably an error - return colorFn(value.stack); - } else { - //something else... lets hope it stringifies - return colorFn(JSON.stringify(value, null, 2)); - } - }); -}; - -export class Logger { - static debug(...params: any[]) { - console.debug(...prefixAndColorize(params, debugColor)); - } - - static log(...params: any[]) { - console.log(...prefixAndColorize(params, logColor)); - } - - static warn(...params: any[]) { - console.warn(...prefixAndColorize(params, warnColor)); - } - - static error(...params: any[]) { - console.error(...prefixAndColorize(params, errorColor)); +export class Logger extends AbstractFDC3Logger { + static override get prefix(): string { + return 'FDC3 getAgent: '; } } diff --git a/packages/fdc3-get-agent/test/step-definitions/util.steps.ts b/packages/fdc3-get-agent/test/step-definitions/util.steps.ts index 1648e6a00..b330186af 100644 --- a/packages/fdc3-get-agent/test/step-definitions/util.steps.ts +++ b/packages/fdc3-get-agent/test/step-definitions/util.steps.ts @@ -1,24 +1,24 @@ -import { Given } from '@cucumber/cucumber'; +import { When } from '@cucumber/cucumber'; import { CustomWorld } from '../world'; import { Logger } from '../../src/util/Logger'; import { createUUID } from '../../src/util/Uuid'; const TEST_ERROR = 'Test error - This is expected on the console'; -Given('All log functions are used with a message', async function (this: CustomWorld) { +When('All log functions are used with a message', async function (this: CustomWorld) { Logger.debug('Debug msg'); Logger.log('Log msg'); Logger.warn('Warning msg'); Logger.error('Error msg'); }); -Given('All log functions are used with an error', async function (this: CustomWorld) { - Logger.debug(new Error(TEST_ERROR)); - Logger.log(new Error(TEST_ERROR)); - Logger.warn(new Error(TEST_ERROR)); - Logger.error(new Error(TEST_ERROR)); +When('All log functions are used with an error', async function (this: CustomWorld) { + Logger.debug('debug-level error: ', new Error(TEST_ERROR)); + Logger.log('log-level error: ', new Error(TEST_ERROR)); + Logger.warn('warn-level error: ', new Error(TEST_ERROR)); + Logger.error('error-level error: ', new Error(TEST_ERROR)); }); -Given('A uuid is generated', async function (this: CustomWorld) { +When('A uuid is generated', async function (this: CustomWorld) { return createUUID(); }); diff --git a/packages/fdc3-standard/src/api/GetAgent.ts b/packages/fdc3-standard/src/api/GetAgent.ts index 33216f2a4..53f50f67d 100644 --- a/packages/fdc3-standard/src/api/GetAgent.ts +++ b/packages/fdc3-standard/src/api/GetAgent.ts @@ -81,6 +81,10 @@ export type GetAgentType = (params?: GetAgentParams) => Promise; * or an iframe's `contentWindow`) for a window or frame in which it has loaded * a Desktop Agent or suitable proxy to one that works with FDC3 Web Connection * and Desktop Agent Communication Protocols. + * + * @property {GetAgentLogSettings} logging Settings that determine what should + * will logged by the getAgent() implementation and DesktopAgentProxy to the + * JavaScript console. */ export type GetAgentParams = { timeoutMs?: number; @@ -89,6 +93,32 @@ export type GetAgentParams = { intentResolver?: boolean; dontSetWindowFdc3?: boolean; failover?: (args: GetAgentParams) => Promise; + logging?: GetAgentLogSettings; +}; + +/** + * @typedef {Object} GetAgentLogSettings Type representing parameters passed to the + * getAgent function that control what is logged to the JavaScript console by the + * getAgent() implementation and any DesktopAgentProxy implementations it creates. + * + * @property {boolean} connection Log-level messages relating to establishing a + * connection to the Desktop Agent (default true). + * + * @property {boolean} connectionDebug Debug-level messages relating to establishing + * a connection to the Desktop Agent (default false). + * + * @property {boolean} proxyDebug Debug-level messages that provide details of + * all messages sent to or received from the DesktopAgent (excluding heartbeat + * messages) by the DesktopAgentProxy (default false). + * + * @property {boolean} heartbeat Debug-level messages relating to heartbeat messages + * sent to or received from the DesktopAgent by the DesktopAgentProxy (default false). + */ +export type GetAgentLogSettings = { + connection: boolean; + connectionDebug: boolean; + proxyDebug: boolean; + heartbeat: boolean; }; /** Type representing the format of data stored by `getAgent` diff --git a/website/docs/api/ref/GetAgent.md b/website/docs/api/ref/GetAgent.md index 5e8e00b5b..48f9ef99b 100644 --- a/website/docs/api/ref/GetAgent.md +++ b/website/docs/api/ref/GetAgent.md @@ -98,14 +98,44 @@ A small number of arguments are accepted that can affect the behavior of `getAge * or an iframe's `contentWindow`) for a window or frame in which it has loaded * a Desktop Agent or suitable proxy to one that works with FDC3 Web Connection * and Desktop Agent Communication Protocols. - */ + * + * @property {GetAgentLogSettings} logging Settings that determine what should + * will logged by the getAgent() implementation and DesktopAgentProxy to the + * JavaScript console. + */ type GetAgentParams = { timeoutMs?: number, identityUrl?: string, channelSelector?: boolean, intentResolver?: boolean, dontSetWindowFdc3?: boolean, - failover?: (args: GetAgentParams) => Promise + failover?: (args: GetAgentParams) => Promise, + logging?: GetAgentLogSettings; +}; + +/** + * @typedef {Object} GetAgentLogSettings Type representing parameters passed to the + * getAgent function that control what is logged to the JavaScript console by the + * getAgent() implementation and any DesktopAgentProxy implementations it creates. + * + * @property {boolean} connection Log-level messages relating to establishing a + * connection to the Desktop Agent (default true). + * + * @property {boolean} connectionDebug Debug-level messages relating to establishing + * a connection to the Desktop Agent (default false). + * + * @property {boolean} proxyDebug Debug-level messages that provide details of + * all messages sent to or received from the DesktopAgent (excluding heartbeat + * messages) by the DesktopAgentProxy (default false). + * + * @property {boolean} heartbeat Debug-level messages relating to heartbeat messages + * sent to or received from the DesktopAgent by the DesktopAgentProxy (default false). + */ +export type GetAgentLogSettings = { + connection: boolean, + connectionDebug: boolean, + proxyDebug: boolean, + heartbeat: boolean }; ``` @@ -159,7 +189,7 @@ Failover functions MUST be asynchronous and MUST resolve to one of the following ## Persisted Connection Data -The `getAgent()` function uses [`SessionStorage`](https://html.spec.whatwg.org/multipage/webstorage.html) ([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)) to persist information on an instance of an app and how it connected to a Desktop Agent in order to ensure a consistent connection type and `instanceId` when reconnecting after navigation or refresh events. +The `getAgent()` function uses [`SessionStorage`](https://html.spec.whatwg.org/multipage/webstorage.html) ([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)) to persist information on an instance of an app and how it connected to a Desktop Agent in order to ensure a consistent connection type and `instanceId` when reconnecting after navigation or refresh events. :::warning Apps do not need to and SHOULD NOT interact with data persisted in SessionStorage by `getAgent` directly, rather it is set and used by the `getAgent()` implementation.