diff --git a/.gitignore b/.gitignore index 3988427b..37ec9f93 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ /src/.baseDir.ts TODO test/app.json +test/client.json +test/service.json /docs cache.dat /cache diff --git a/Gruntfile.js b/Gruntfile.js index 95113f0a..0791f54c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -124,13 +124,22 @@ module.exports = function (grunt) { } }); + //using the node webpack API: https://webpack.js.org/api/node/ + //will use Task Error or Warning grunt codes based on webpack results: https://gruntjs.com/exit-codes grunt.registerTask('webpack', function () { const done = this.async(); webpack(webpackConfig, (err, stats) => { - if (err || stats.hasErrors()) { + if (err) { const error = err ? err.message : 'webpack error'; - grunt.log.error(error); - done(err); + if (err.details) { + grunt.fail.fatal(err.details, 3); + } + } else if(stats.hasErrors()) { + const info = stats.toJson(); + grunt.fail.fatal(info.errors, 3); + } else if(stats.hasWarnings()) { + const info = stats.toJson(); + grunt.fail.warn(info.warnings, 6); } else { grunt.log.ok('webpack task done'); done(); diff --git a/html/client.html b/html/client.html new file mode 100644 index 00000000..527886f7 --- /dev/null +++ b/html/client.html @@ -0,0 +1,23 @@ + + + Service Client + + + +

Service Client

+ +
+ + + + + \ No newline at end of file diff --git a/html/service.html b/html/service.html new file mode 100644 index 00000000..37e0e241 --- /dev/null +++ b/html/service.html @@ -0,0 +1,24 @@ + + + Service Provider + + + +

Service Provider

+ +
+ + + + + \ No newline at end of file diff --git a/package.json b/package.json index ea7a76f5..bf363494 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hadouken-js-adapter", - "version": "0.30.2", + "version": "0.31.1", "license": "Apache-2.0", "repository": "https://github.com/HadoukenIO/js-adapter", "main": "./out/src/main.js", diff --git a/src/api/application/application.ts b/src/api/application/application.ts index eb6960e4..8349f2bb 100644 --- a/src/api/application/application.ts +++ b/src/api/application/application.ts @@ -1,4 +1,4 @@ -import { Base, Bare, Reply, RuntimeEvent } from '../base'; +import { EmitterBase, Bare, Reply, RuntimeEvent } from '../base'; import { Identity } from '../../identity'; import { _Window } from '../window/window'; import { Point } from '../system/point'; @@ -79,24 +79,11 @@ export default class ApplicationModule extends Bare { * execute, show/close an application as well as listen to application events. * @class */ -export class Application extends Base { + // @ts-ignore: return types incompatible with EventEmitter (this) +export class Application extends EmitterBase { constructor(wire: Transport, public identity: Identity) { super(wire); - - this.on('removeListener', eventType => { - this.deregisterEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); - - this.on('newListener', eventType => { - this.registerEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); } protected runtimeEventComparator = (listener: RuntimeEvent): boolean => { @@ -311,21 +298,22 @@ export class Application extends Base { } +// @ts-ignore: return types incompatible with EventEmitter (this) export interface Application { - on(type: 'closed', listener: (data: Reply<'application', 'closed'>) => void): this; - on(type: 'initialized', listener: (data: Reply<'application', 'initialized'>) => void): this; - on(type: 'connected', listener: (data: Reply<'application', 'connected'>) => void): this; - on(type: 'crashed', listener: (data: Reply<'application', 'crashed'>) => void): this; - on(type: 'error', listener: (data: Reply<'application', 'error'>) => void): this; - on(type: 'not-responding', listener: (data: Reply<'application', 'not-responding'>) => void): this; - on(type: 'out-of-memory', listener: (data: Reply<'application', 'out-of-memory'>) => void): this; - on(type: 'responding', listener: (data: Reply<'application', 'responding'>) => void): this; - on(type: 'started', listener: (data: Reply<'application', 'started'>) => void): this; - on(type: 'run-requested', listener: (data: Reply<'application', 'run-requested'>) => void): this; - on(type: 'window-navigation-rejected', listener: (data: NavigationRejectedReply) => void): this; - on(type: 'window-created', listener: (data: Reply<'application', 'window-created'>) => void): this; - on(type: 'window-closed', listener: (data: Reply<'application', 'window-closed'>) => void): this; - on(type: 'tray-icon-clicked', listener: (data: TrayIconClickReply) => void): this; - on(type: 'removeListener', listener: (eventType: string) => void): this; - on(type: 'newListener', listener: (eventType: string) => void): this; + on(type: 'closed', listener: (data: Reply<'application', 'closed'>) => void): Promise; + on(type: 'initialized', listener: (data: Reply<'application', 'initialized'>) => void): Promise; + on(type: 'connected', listener: (data: Reply<'application', 'connected'>) => void): Promise; + on(type: 'crashed', listener: (data: Reply<'application', 'crashed'>) => void): Promise; + on(type: 'error', listener: (data: Reply<'application', 'error'>) => void): Promise; + on(type: 'not-responding', listener: (data: Reply<'application', 'not-responding'>) => void): Promise; + on(type: 'out-of-memory', listener: (data: Reply<'application', 'out-of-memory'>) => void): Promise; + on(type: 'responding', listener: (data: Reply<'application', 'responding'>) => void): Promise; + on(type: 'started', listener: (data: Reply<'application', 'started'>) => void): Promise; + on(type: 'run-requested', listener: (data: Reply<'application', 'run-requested'>) => void): Promise; + on(type: 'window-navigation-rejected', listener: (data: NavigationRejectedReply) => void): Promise; + on(type: 'window-created', listener: (data: Reply<'application', 'window-created'>) => void): Promise; + on(type: 'window-closed', listener: (data: Reply<'application', 'window-closed'>) => void): Promise; + on(type: 'tray-icon-clicked', listener: (data: TrayIconClickReply) => void): Promise; + on(type: 'removeListener', listener: (eventType: string) => void): Promise; + on(type: 'newListener', listener: (eventType: string) => void): Promise; } diff --git a/src/api/base.ts b/src/api/base.ts index 10e419a5..f3365575 100644 --- a/src/api/base.ts +++ b/src/api/base.ts @@ -1,10 +1,11 @@ import Transport, { Message } from '../transport/transport'; import { Identity } from '../identity'; import { EventEmitter } from 'events'; +import { promiseMap } from '../util/promises'; export interface RuntimeEvent extends Identity { topic: string; - type: string; + type: string|symbol; } export class Bare extends EventEmitter { @@ -32,8 +33,6 @@ export class Bare extends EventEmitter { } export class Base extends Bare { - protected identity: Identity; - constructor(wire: Transport) { super(wire); wire.registerMessageHandler(this.onmessage.bind(this)); @@ -57,19 +56,20 @@ export class Base extends Bare { } } - protected registerEventListener = (listener: RuntimeEvent): void => { + protected registerEventListener = (listener: RuntimeEvent): Promise> => { const key = createKey(listener); const refCount = this.wire.topicRefMap.get(key); if (!refCount) { this.wire.topicRefMap.set(key, 1); - this.wire.sendAction('subscribe-to-desktop-event', listener); + return this.wire.sendAction('subscribe-to-desktop-event', listener); } else { this.wire.topicRefMap.set(key, refCount + 1); + return Promise.resolve(); } } - protected deregisterEventListener = (listener: RuntimeEvent): void => { + protected deregisterEventListener = (listener: RuntimeEvent): Promise> => { const key = createKey(listener); const refCount = this.wire.topicRefMap.get(key); @@ -79,12 +79,104 @@ export class Base extends Bare { this.wire.topicRefMap.set(key, newRefCount); if (newRefCount === 0) { - this.wire.sendAction('unsubscribe-to-desktop-event', listener); + return this.wire.sendAction('unsubscribe-to-desktop-event', listener); } + return Promise.resolve(); } + } +} + +// @ts-ignore: return types incompatible with EventEmitter (this) +export class EmitterBase extends Base { + protected identity: Identity; + // @ts-ignore: return types incompatible with EventEmitter (this) + public on(eventType: string, listener: (...args: any[]) => void): Promise { + super.on(eventType, listener); + return this.registerEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })).then(() => undefined); + } + // @ts-ignore: return types incompatible with EventEmitter (this) + public addListener = this.on; + //@ts-ignore: return types incompatible with EventEmitter (this) + public once(eventType: string, listener: (...args: any[]) => void): Promise { + super.once(eventType, listener); + const deregister = () => { + this.deregisterEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })); + }; + super.once(eventType, deregister); + return this.registerEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })).then(() => undefined); + } + // @ts-ignore: return types incompatible with EventEmitter (this) + public prependListener(eventType: string, listener: (...args: any[]) => void): Promise { + super.prependListener(eventType, listener); + return this.registerEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })).then(() => undefined); + } + // @ts-ignore: return types incompatible with EventEmitter (this) + public prependOnceListener(eventType: string, listener: (...args: any[]) => void): Promise { + super.prependOnceListener(eventType, listener); + const deregister = () => { + this.deregisterEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })); + }; + super.once(eventType, deregister); + return this.registerEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })).then(() => undefined); + } + // @ts-ignore: return types incompatible with EventEmitter (this) + public removeListener(eventType: string, listener: (...args: any[]) => void): Promise { + super.removeListener(eventType, listener); + return this.deregisterEventListener(Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + })).then(() => undefined); } + protected deregisterAllListeners = (eventType: string|symbol): Promise> => { + const runtimeEvent = Object.assign({}, this.identity, { + type: eventType, + topic: this.topic + }); + const key = createKey(runtimeEvent); + const refCount = this.wire.topicRefMap.get(key); + + if (refCount) { + this.wire.topicRefMap.delete(key); + return this.wire.sendAction('unsubscribe-to-desktop-event', runtimeEvent); + } else { + return Promise.resolve(); + } + } + // @ts-ignore: return types incompatible with EventEmitter (this) + public async removeAllListeners(eventType?: string): Promise { + + const removeByEvent = (event: string|symbol): Promise => { + super.removeAllListeners(event); + return this.deregisterAllListeners(event).then(() => undefined); + }; + + if (eventType) { + return removeByEvent(eventType); + } else { + const events = this.eventNames(); + await promiseMap(events, removeByEvent); + } + } } export class Reply implements Identity { diff --git a/src/api/external-application/external-application.ts b/src/api/external-application/external-application.ts index b2e63208..ffd37572 100644 --- a/src/api/external-application/external-application.ts +++ b/src/api/external-application/external-application.ts @@ -1,4 +1,4 @@ -import { Bare, Base, Reply } from '../base'; +import { Bare, EmitterBase, Reply } from '../base'; import { Identity } from '../../identity'; import Transport from '../../transport/transport'; @@ -23,24 +23,11 @@ export default class ExternalApplicationModule extends Bare { * well as listen to application events. * @class */ -export class ExternalApplication extends Base { +// @ts-ignore: return types incompatible with EventEmitter (this) +export class ExternalApplication extends EmitterBase { constructor(wire: Transport, public identity: Identity) { super(wire); - - this.on('removeListener', eventType => { - this.deregisterEventListener(Object.assign({}, this.identity, { - type: eventType, - topic : this.topic - })); - }); - - this.on('newListener', eventType => { - this.registerEventListener(Object.assign({}, this.identity, { - type: eventType, - topic : this.topic - })); - }); } /** @@ -53,9 +40,10 @@ export class ExternalApplication extends Base { } } +// @ts-ignore: return types incompatible with EventEmitter (this) export interface ExternalApplication { - on(type: 'connected', listener: (data: Reply<'externalapplication', 'connected'>) => void): this; - on(type: 'disconnected', listener: (data: Reply<'externalapplication', 'disconnected'>) => void): this; - on(type: 'removeListener', listener: (eventType: string) => void): this; - on(type: 'newListener', listener: (eventType: string) => void): this; + on(type: 'connected', listener: (data: Reply<'externalapplication', 'connected'>) => void): Promise; + on(type: 'disconnected', listener: (data: Reply<'externalapplication', 'disconnected'>) => void): Promise; + on(type: 'removeListener', listener: (eventType: string) => void): Promise; + on(type: 'newListener', listener: (eventType: string) => void): Promise; } diff --git a/src/api/fin.ts b/src/api/fin.ts index a80b4591..3738075d 100644 --- a/src/api/fin.ts +++ b/src/api/fin.ts @@ -9,6 +9,7 @@ import Clipbpard from './clipboard/clipboard'; import ExternalApplication from './external-application/external-application'; import _FrameModule from './frame/frame'; import Plugin from './plugin/plugin'; +import { Service } from './services'; export default class Fin extends Bare { public System: System; @@ -20,6 +21,7 @@ export default class Fin extends Bare { public ExternalApplication: ExternalApplication; public Frame: _FrameModule; public Plugin: Plugin; + public Service: Service; constructor(wire: Transport, public token: string) { super(wire); @@ -32,6 +34,7 @@ export default class Fin extends Bare { this.ExternalApplication = new ExternalApplication(wire); this.Frame = new _FrameModule(wire); this.Plugin = new Plugin(wire); + this.Service = new Service(wire); //Handle disconnect events wire.on('disconnected', () => { diff --git a/src/api/frame/frame.ts b/src/api/frame/frame.ts index c0d6047e..2017f823 100644 --- a/src/api/frame/frame.ts +++ b/src/api/frame/frame.ts @@ -1,4 +1,4 @@ -import { Bare, Base } from '../base'; +import { Bare, EmitterBase } from '../base'; import { Identity } from '../../identity'; import Transport from '../../transport/transport'; @@ -38,25 +38,12 @@ export default class _FrameModule extends Bare { * @class * @alias Frame */ +// @ts-ignore: return types incompatible with EventEmitter (this) // tslint:disable-next-line -export class _Frame extends Base { +export class _Frame extends EmitterBase { constructor(wire: Transport, public identity: Identity) { super(wire); - - this.on('removeListener', eventType => { - this.deregisterEventListener(Object.assign({}, this.identity, { - type: eventType, - topic : this.topic - })); - }); - - this.on('newListener', eventType => { - this.registerEventListener(Object.assign({}, this.identity, { - type: eventType, - topic : this.topic - })); - }); } /** @@ -80,8 +67,9 @@ export class _Frame extends Base { } +// @ts-ignore: return types incompatible with EventEmitter (this) // tslint:disable-next-line export interface _Frame { - on(type: 'removeListener', listener: (eventType: string) => void): this; - on(type: 'newListener', listener: (eventType: string) => void): this; + on(type: 'connected', listener: (eventType: string) => void): Promise; + on(type: 'disconnected', listener: (eventType: string) => void): Promise; } diff --git a/src/api/plugin/plugin.ts b/src/api/plugin/plugin.ts index c30f3cd9..c145e9e7 100644 --- a/src/api/plugin/plugin.ts +++ b/src/api/plugin/plugin.ts @@ -2,11 +2,6 @@ import { Base } from '../base'; import Transport from '../../transport/transport'; import { notImplementedEnvErrorMsg } from '../../environment/environment'; -export interface PluginBare { - name: string; - version: string; -} - /** * The Plugin API allows importing OpenFin plugins * @namespace @@ -32,13 +27,11 @@ export default class Plugin extends Base { * **Important**: If you set HTTP Content-Security-Policy's `script-src` directive * you must allow `unsafe-inline` for `blob:` for this API to work. * - * @param {Object} plugin - Plugin to import. Specified plugin must be listed in app's manifest. - * @param {string} plugin.name - plugin name - * @param {string} plugin.version - plugin version + * @param {string} name - Plugin to import. Specified plugin must be listed in app's manifest. * @return {Promise} * @tutorial Plugin.import */ - public async import(plugin: PluginBare): Promise { + public async import(name: string): Promise { if (!this.isOpenFinEnvironment()) { throw new Error(notImplementedEnvErrorMsg); } @@ -47,10 +40,10 @@ export default class Plugin extends Base { throw new Error(this.noEsmSupportErrorMsg); } - const { payload } = await this.wire.sendAction('get-plugin-module', { plugin }); - const { data: { _content } } = payload; + const { payload } = await this.wire.sendAction('get-plugin-module', name); + const { data: content } = payload; - return this.importModule(_content); + return this.importModule(content); } // ESM is supported in OF v9+ diff --git a/src/api/services/channel.ts b/src/api/services/channel.ts new file mode 100644 index 00000000..5d9fb95f --- /dev/null +++ b/src/api/services/channel.ts @@ -0,0 +1,107 @@ +import { Identity } from '../../identity'; +import Transport, { Message } from '../../transport/transport'; + +const idOrResult = (func: (...args: any[]) => any) => (...args: any[] ) => { + const res = func(...args); + return res === undefined ? args[1] : res; +}; + +//tslint:disable-next-line +export interface ServiceIdentity extends Identity {} + +export type Action = (() => any) + | ((payload: any) => any) + | ((payload: any, id: ServiceIdentity) => any); +export type Middleware = (() => any) + | ((action: string) => any) + | ((action: string, payload: any) => any) + | ((action: string, payload: any, id: ServiceIdentity) => any); + +export interface ServiceMessagePayload extends Identity { + action: string; + payload: any; +} + +export class ServiceChannel { + protected subscriptions: any; + public defaultAction: (action?: string, payload?: any, senderIdentity?: ServiceIdentity) => any; + private preAction: (...args: any[]) => any; + private postAction: (...args: any[]) => any; + private errorMiddleware: (...args: any[]) => any; + private defaultSet: boolean; + protected send: (to: Identity, action: string, payload: any) => Promise>; + + constructor (send: Transport['sendAction']) { + this.defaultSet = false; + this.subscriptions = new Map any>(); + this.defaultAction = () => { + throw new Error('No action registered'); + }; + this.send = async (to: Identity, action: string, payload: any) => { + const raw = await send('send-service-message', { ...to, action, payload }).catch(reason => { + throw new Error(reason.message); + }); + return raw.payload.data.result; + }; + } + + public async processAction(action: string, payload: any, senderIdentity: ServiceIdentity) { + try { + const mainAction = this.subscriptions.has(action) + ? this.subscriptions.get(action) + : (payload: any, id: ServiceIdentity) => this.defaultAction(action, payload, id); + const preActionProcessed = this.preAction ? await this.preAction(action, payload, senderIdentity) : payload; + const actionProcessed = await mainAction(preActionProcessed, senderIdentity); + return this.postAction + ? await this.postAction(action, actionProcessed, senderIdentity) + : actionProcessed; + } catch (e) { + if (this.errorMiddleware) { + return this.errorMiddleware(action, e, senderIdentity); + } throw e; + } + } + + public beforeAction(func: Action) { + if (this.preAction) { + throw new Error('Already registered beforeAction middleware'); + } + this.preAction = idOrResult(func); + } + + public onError(func: (e: any, action: string, id: Identity) => any) { + if (this.errorMiddleware) { + throw new Error('Already registered error middleware'); + } + this.errorMiddleware = func; + } + + public afterAction(func: Action) { + if (this.postAction) { + throw new Error('Already registered afterAction middleware'); + } + this.postAction = idOrResult(func); + } + + public remove(action: string): void { + this.subscriptions.delete(action); + } + + public setDefaultAction(func: (action?: string, payload?: any, senderIdentity?: ServiceIdentity) => any): void { + if (this.defaultSet) { + throw new Error('default action can only be set once'); + } else { + this.defaultAction = func; + this.defaultSet = true; + } + } + + public register(topic: string, listener: Action) { + if (this.subscriptions.has(topic)) { + throw new Error(`Subscription already registered for action: ${topic}. Unsubscribe before adding new subscription`); + } else { + this.subscriptions.set(topic, listener); + return true; + } + } +} \ No newline at end of file diff --git a/src/api/services/client.ts b/src/api/services/client.ts new file mode 100644 index 00000000..ba05993c --- /dev/null +++ b/src/api/services/client.ts @@ -0,0 +1,14 @@ +import { ServiceChannel, ServiceIdentity } from './channel'; +import Transport from '../../transport/transport'; + +export class Client extends ServiceChannel { + public onServiceDisconnect: (f: () => void) => void; + constructor(private identity: ServiceIdentity, send: Transport['sendAction']) { + super(send); + } + + public async dispatch(action: string, payload?: any): Promise { + return this.send(this.identity, action, payload); + } + +} diff --git a/src/api/services/index.ts b/src/api/services/index.ts new file mode 100644 index 00000000..054b3ae7 --- /dev/null +++ b/src/api/services/index.ts @@ -0,0 +1,107 @@ +import { Client } from './client'; +import { Identity } from '../../identity'; +import { Provider } from './provider'; +import { Base } from '../base'; +import Transport, { Message, Payload } from '../../transport/transport'; + +export interface Options { + wait?: boolean; + uuid: string; + payload?: any; +} + +export interface ServicePayload { + payload: Payload; +} +export interface ServiceMessage extends Message { + senderIdentity: Identity; + ackToSender: any; + serviceIdentity: Identity; + connectAction: boolean; +} + +export class Service extends Base { + private serviceMap: Map; + constructor(wire: Transport) { + super(wire); + this.serviceMap = new Map(); + wire.registerMessageHandler(this.onmessage.bind(this)); + } + + public async onServiceConnect(identity: Identity, listener: EventListener): Promise { + this.registerEventListener({ + topic: 'service', + type: 'connected', + ...identity + }); + this.on('connected', listener); + } + + public async connect(options: Options): Promise { + try { + const { payload: { data: serviceIdentity } } = await this.wire.sendAction('send-service-message', Object.assign({ + connectAction: true, + wait: true + }, options)); + const channel = new Client(serviceIdentity, this.wire.sendAction.bind(this.wire)); + channel.onServiceDisconnect = (listener: () => void) => { + this.registerEventListener({ + topic: 'service', + type: 'disconnected', + ...serviceIdentity + }); + this.on('disconnected', listener); + }; + this.serviceMap.set(serviceIdentity.uuid, channel); + return channel; + } catch (e) { + throw new Error(e.message); + } + } + + public async register(): Promise { + const { payload: { data: serviceIdentity } } = await this.wire.sendAction('register-service', {}); + const channel = new Provider(this.wire.sendAction.bind(this.wire)); + this.serviceMap.set(serviceIdentity.uuid, channel); + return channel; + } + public onmessage = (msg: ServiceMessage) => { + if (msg.action === 'process-service-action') { + this.processServiceMessage(msg); + return true; + } + return false; + } + private async processServiceMessage (msg: ServiceMessage) { + const { senderIdentity, serviceIdentity, action, ackToSender, payload, connectAction} = msg.payload; + const bus = this.serviceMap.get(serviceIdentity.uuid); + try { + let res; + if (!bus) { + return; + } + if (connectAction) { + if (!(bus instanceof Provider)) { + throw Error('Cannot connect to a plugin'); + } + res = await bus.processConnection(senderIdentity, payload); + } else { + res = await bus.processAction(action, payload, senderIdentity); + } + ackToSender.payload.payload = ackToSender.payload.payload || {}; + ackToSender.payload.payload.result = res; + this.wire.sendRaw(ackToSender); + } catch (e) { + ackToSender.success = false; + ackToSender.reason = e.message; + this.wire.sendRaw(ackToSender); + } + } + +} + +interface PluginSubscribeSuccess { + uuid: string; + name: string; + serviceName: string; +} diff --git a/src/api/services/provider.ts b/src/api/services/provider.ts new file mode 100644 index 00000000..4aade956 --- /dev/null +++ b/src/api/services/provider.ts @@ -0,0 +1,33 @@ +import { ServiceChannel, ServiceIdentity } from './channel'; +import Transport from '../../transport/transport'; + +export type ConnectionListener = (adapterIdentity: ServiceIdentity, connectionMessage?: any) => any; + +export class Provider extends ServiceChannel { + private connectListener: ConnectionListener; + private connections: ServiceIdentity[]; + + constructor(send: Transport['sendAction']) { + super(send); + this.connectListener = () => undefined; + this.connections = []; + } + + public dispatch(to: ServiceIdentity, action: string, payload: any): Promise { + return this.send(to, action, payload); + } + + public async processConnection(senderId: ServiceIdentity, payload: any) { + this.connections.push(senderId); + return this.connectListener(senderId, payload); + } + + public publish(action: string, payload: any): Promise[] { + return this.connections.map(to => this.send(to, action, payload)); + } + + public onConnection(listener: ConnectionListener): void { + this.connectListener = listener; + } + +} \ No newline at end of file diff --git a/src/api/system/system.ts b/src/api/system/system.ts index 5757c835..25a7894e 100644 --- a/src/api/system/system.ts +++ b/src/api/system/system.ts @@ -1,4 +1,4 @@ -import { Base } from '../base'; +import { EmitterBase } from '../base'; import { ApplicationInfo } from './application'; import { WindowInfo } from './window'; import { Identity } from '../../identity'; @@ -164,25 +164,11 @@ import { RuntimeError, NotSupportedError } from '../../transport/transport-error * clearing the cache and exiting the runtime. * @namespace */ -export default class System extends Base { +// @ts-ignore: return types incompatible with EventEmitter (this) +export default class System extends EmitterBase { constructor(wire: Transport) { super(wire); - - this.on('removeListener', (eventType: string) => { - this.deregisterEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); - - this.on('newListener', (eventType: string) => { - this.registerEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); - } /** diff --git a/src/api/window/window.ts b/src/api/window/window.ts index 09801ab9..49eee221 100644 --- a/src/api/window/window.ts +++ b/src/api/window/window.ts @@ -1,4 +1,4 @@ -import { Bare, Base, RuntimeEvent } from '../base'; +import { Bare, EmitterBase, RuntimeEvent } from '../base'; import { Identity } from '../../identity'; import Bounds from './bounds'; import BoundsChangedReply from './bounds-changed'; @@ -161,8 +161,9 @@ this animation onto the end of the animation queue. * @alias Window */ // The window.Window name is taken +// @ts-ignore: return types incompatible with EventEmitter (this) // tslint:disable-next-line -export class _Window extends Base { +export class _Window extends EmitterBase { /** * Raised when a window within this application requires credentials from the user. * @@ -541,20 +542,6 @@ export class _Window extends Base { */ constructor(wire: Transport, public identity: Identity) { super(wire); - - this.on('removeListener', eventType => { - this.deregisterEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); - - this.on('newListener', eventType => { - this.registerEventListener(Object.assign({}, this.identity, { - type: eventType, - topic: this.topic - })); - }); } protected runtimeEventComparator = (listener: RuntimeEvent): boolean => { @@ -1054,15 +1041,15 @@ export class _Window extends Base { } } - +// @ts-ignore: "on" return types incompatible with EventEmitter (this) // tslint:disable-next-line export interface _Window { - on(type: 'focused', listener: Function): this; - on(type: 'initialized', listener: Function): this; - on(type: 'bounds-changed', listener: (data: BoundsChangedReply) => void): this; - on(type: 'hidden', listener: Function): this; - on(type: 'removeListener', listener: (eventType: string) => void): this; - on(type: 'newListener', listener: (eventType: string) => void): this; - on(type: 'closed', listener: (eventType: CloseEventShape) => void): this; - on(type: 'fire-constructor-callback', listener: Function): this; + on(type: 'focused', listener: Function): Promise; + on(type: 'initialized', listener: Function): Promise; + on(type: 'bounds-changed', listener: (data: BoundsChangedReply) => void): Promise; + on(type: 'hidden', listener: Function): Promise; + on(type: 'removeListener', listener: (eventType: string | symbol) => void): Promise; + on(type: 'newListener', listener: (eventType: string | symbol) => void): Promise; + on(type: 'closed', listener: (eventType: CloseEventShape) => void): Promise; + on(type: 'fire-constructor-callback', listener: Function): Promise; } diff --git a/src/environment/openfin-env.ts b/src/environment/openfin-env.ts index 7a8c3fef..77c6a790 100644 --- a/src/environment/openfin-env.ts +++ b/src/environment/openfin-env.ts @@ -39,6 +39,10 @@ export default class OpenFinEnvironment implements Environment { return reject(new Error('Trying to create a window that already exists')); } + // we should register the window name with the core asap to prevent + // multiple windows claiming the same uuid-name combo + fin.__internal_.registerWindowName(opt.uuid, opt.name); + if (opt.url !== ABOUT_BLANK) { opt.url = this.resolveUrl(opt.url); } diff --git a/src/launcher/nix-launch.ts b/src/launcher/nix-launch.ts index 95b4d6fb..8dc941ea 100644 --- a/src/launcher/nix-launch.ts +++ b/src/launcher/nix-launch.ts @@ -2,7 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { ChildProcess, spawn } from 'child_process'; import { ConfigWithRuntime } from '../transport/wire'; -import { promisify, resolveRuntimeVersion, rmDir, downloadFile, unzip, resolveDir, exists } from './util'; +import { promisify } from '../util/promises'; +import { resolveRuntimeVersion, rmDir, downloadFile, unzip, resolveDir, exists } from './util'; const mkdir = promisify(fs.mkdir); diff --git a/src/launcher/util.ts b/src/launcher/util.ts index d4e40c9c..900e678d 100644 --- a/src/launcher/util.ts +++ b/src/launcher/util.ts @@ -2,12 +2,7 @@ import * as path from 'path'; import * as https from 'https'; import * as fs from 'fs'; import { exec } from 'child_process'; - -export function promisify(func: Function): (...args: any[]) => Promise { - return (...args: any[]) => new Promise((resolve, reject) => { - func(...args, (err: Error, val: any) => err ? reject(err) : resolve(val)); - }); -} +import { promisify, promiseMap } from '../util/promises'; const stat = promisify(fs.stat); export async function exists(path: string): Promise { @@ -149,20 +144,3 @@ export async function resolveDir(base: string, paths: string[]): Promise } }, Promise.resolve(base)); } - -export async function promiseMap(arr: T[], asyncF: (x: T, i: number, r: T[]) => Promise): Promise { - return Promise.all(arr.map(asyncF)); -} - -export type asyncF = (...args: any[]) => Promise; -export async function serial(arr: asyncF[]): Promise { - const ret: T[] = []; - for (const func of arr) { - const next = await func(); - ret.push(next); - } - return ret; -} -export async function promiseMapSerial(arr: any[], func: asyncF): Promise { - return serial(arr.map((value, index, array) => () => func(value, index, array))); -} diff --git a/src/transport/transport.ts b/src/transport/transport.ts index f8e2f6d1..68e8d8b8 100644 --- a/src/transport/transport.ts +++ b/src/transport/transport.ts @@ -20,9 +20,7 @@ import { declare var fin: any; -export interface MessageHandler { - (data: Function): boolean; -} +export type MessageHandler = (data: any) => boolean; class Transport extends EventEmitter { protected wireListeners: Map = new Map(); @@ -32,11 +30,13 @@ class Transport extends EventEmitter { protected wire: Wire; public environment: Environment; public topicRefMap: Map = new Map(); + public sendRaw: Wire['send']; constructor(wireType: WireConstructor, environment: Environment) { super(); this.wire = new wireType(this.onmessage.bind(this)); this.environment = environment; + this.sendRaw = this.wire.send.bind(this.wire); this.registerMessageHandler(this.handleMessage.bind(this)); this.wire.on('disconnected', () => { diff --git a/src/util/normalize-config.ts b/src/util/normalize-config.ts index 1baad319..57f7f1db 100644 --- a/src/util/normalize-config.ts +++ b/src/util/normalize-config.ts @@ -2,7 +2,7 @@ import { ConnectConfig, isExternalConfig, InternalConnectConfig, ExternalConfig, import { Url, parse } from 'url'; import { IncomingMessage } from 'http'; import * as fs from 'fs'; -import { promisify } from '../launcher/util'; +import { promisify } from '../util/promises'; async function readLocalConfig(location: string): Promise { const txt = await promisify(fs.readFile)(location); @@ -59,4 +59,4 @@ export async function validateConfig(config: ConnectConfig) { } else { throw new Error('Invalid Config'); } -} \ No newline at end of file +} diff --git a/src/util/promises.ts b/src/util/promises.ts new file mode 100644 index 00000000..6693d479 --- /dev/null +++ b/src/util/promises.ts @@ -0,0 +1,22 @@ +export function promisify(func: Function): (...args: any[]) => Promise { + return (...args: any[]) => new Promise((resolve, reject) => { + func(...args, (err: Error, val: any) => err ? reject(err) : resolve(val)); + }); +} + +export async function promiseMap(arr: T[], asyncF: (x: T, i: number, r: T[]) => Promise): Promise { + return Promise.all(arr.map(asyncF)); +} + +export type asyncF = (...args: any[]) => Promise; +export async function serial(arr: asyncF[]): Promise { + const ret: T[] = []; + for (const func of arr) { + const next = await func(); + ret.push(next); + } + return ret; +} +export async function promiseMapSerial(arr: any[], func: asyncF): Promise { + return serial(arr.map((value, index, array) => () => func(value, index, array))); +} diff --git a/test/application.test.ts b/test/application.test.ts index dd28cf55..571ea1cc 100644 --- a/test/application.test.ts +++ b/test/application.test.ts @@ -1,5 +1,6 @@ import { conn } from './connect'; import { Fin, Application, connect as rawConnect } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; import * as assert from 'assert'; import * as path from 'path'; @@ -11,10 +12,10 @@ describe('Application.', function() { this.timeout(30000); let counter = 0; - before(() => conn().then((a: Fin) => { - - fin = a; - })); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); + }); beforeEach(async () => { testApp = await fin.Application.create({ diff --git a/test/clipboard.test.ts b/test/clipboard.test.ts index 17b0a6f1..bfb55948 100644 --- a/test/clipboard.test.ts +++ b/test/clipboard.test.ts @@ -1,6 +1,7 @@ import { conn } from './connect'; import * as assert from 'assert'; import { Fin } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('Clipboard.', () => { let fin: Fin; @@ -17,8 +18,9 @@ describe('Clipboard.', () => { } }; - before(() => { - return conn().then((a: Fin) => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); describe('writeText()', () => { diff --git a/test/connect.test.ts b/test/connect.test.ts index c108cd01..c3937ae1 100644 --- a/test/connect.test.ts +++ b/test/connect.test.ts @@ -1,11 +1,13 @@ import { conn } from './connect'; import * as assert from 'assert'; import { connect as rawConnect, Fin } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('connect()', () => { let fin: Fin; - before(() => { - return conn().then((a: Fin) => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); it('authentication', () => { assert(fin.System !== undefined); diff --git a/test/connect.ts b/test/connect.ts index 10783d6a..c94babaf 100644 --- a/test/connect.ts +++ b/test/connect.ts @@ -1,5 +1,4 @@ import { connect, Fin } from '../src/main'; -import { kill } from './multi-runtime-utils'; const MAX_TRY_NUMBER = 5; let c: Promise; @@ -24,11 +23,3 @@ export function conn() { return c; } - -export async function clean() { - if (c) { - const f = await c; - kill(f); - c = null; - } -} diff --git a/test/event-emitter.test.ts b/test/event-emitter.test.ts new file mode 100644 index 00000000..dc052189 --- /dev/null +++ b/test/event-emitter.test.ts @@ -0,0 +1,83 @@ +import * as assert from 'assert'; +import { conn } from './connect'; +import { Fin } from '../src/main'; +import * as sinon from 'sinon'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; + +describe ('Event Emitter Methods', () => { + let fin: Fin; + let app: any; + let win: any; + const appConfigTemplate = { + name: 'adapter-test-app', + url: 'about:blank', + uuid: 'adapter-test-app', + autoShow: true, + saveWindowState: false, + accelerator: { + devtools: true + } + }; + + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); + }); + + beforeEach(async () => { + app = await fin.Application.create(appConfigTemplate); + await app.run(); + win = await app.getWindow(); + }); + + afterEach(async() => { + await app.close(); + }); + + describe('once', () => { + it('should only get called once then removed', async () => { + const spy = sinon.spy(); + await win.once('bounds-changed', spy); + await win.moveBy(1, 1); + await win.moveBy(1, 1); + assert(spy.calledOnce); + }); + }); + + describe('removeAllListeners', () => { + it('should remove listeners for a given event', async () => { + const boundsSpy = sinon.spy(); + const closedSpy = sinon.spy(); + await win.addListener('bounds-changed', boundsSpy); + await win.addListener('closed', closedSpy); + await win.moveBy(1, 1); + await win.removeAllListeners('bounds-changed'); + await win.moveBy(1, 1); + const eventNames = win.eventNames(); + await win.close(); + assert(eventNames.length === 1, `Expected ${eventNames} to be closed and only closed`); + assert(boundsSpy.calledOnce); + assert(closedSpy.calledOnce); + }); + + it('should remove listeners for all events', async () => { + const boundsSpy = sinon.spy(); + const closedSpy = sinon.spy(); + await win.addListener('bounds-changed', boundsSpy); + await win.on('closed', closedSpy); + await win.moveBy(1, 1); + await win.removeAllListeners(); + const noEvents = win.eventNames(); + await win.moveBy(1, 1); + await win.on('bounds-changed', boundsSpy); + const eventNames = win.eventNames(); + await win.moveBy(1, 1); + await win.close(); + assert(boundsSpy.calledTwice); + assert(closedSpy.notCalled); + assert(eventNames.length === 1, `Expected ${eventNames} to be bounds-changed and only bounds-changed`); + assert(noEvents.length === 0, `Expected ${eventNames} event to not exist`); + }); + }); + +}); diff --git a/test/external-application.test.ts b/test/external-application.test.ts index 3be937b3..56c2e8a2 100644 --- a/test/external-application.test.ts +++ b/test/external-application.test.ts @@ -1,10 +1,14 @@ import { conn } from './connect'; import { Fin } from '../src/main'; import * as assert from 'assert'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('ExternalApplication.', () => { let fin: Fin; - before(() => conn().then((a: Fin) => fin = a)); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); + }); describe('getInfo()', () => { it('Fulfilled', () => fin.System.getAllExternalApplications(). diff --git a/test/external-services.test.ts b/test/external-services.test.ts new file mode 100644 index 00000000..8fd0923b --- /dev/null +++ b/test/external-services.test.ts @@ -0,0 +1,118 @@ +import * as assert from 'assert'; +import { conn } from './connect'; +import { Fin, launch } from '../src/main'; +import * as path from 'path'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; +import { delayPromise } from './delay-promise'; +import * as sinon from 'sinon'; +import * as fs from 'fs'; + +describe ('External Services', () => { + let fin: Fin; + let appConfig: any; + + beforeEach(async () => { + await cleanOpenRuntimes(); + appConfig = JSON.parse(fs.readFileSync(path.resolve('test/app.json')).toString()); + fin = await conn(); + }); + + after(async () => { + const apps = await fin.System.getAllApplications(); + await Promise.all(apps.map(a => { + const { uuid } = a; + return fin.Application.wrap({uuid}).then(app => app.close()); + })); + }); + + // tslint:disable-next-line + describe('External Provider', function () { + + it('Should be able to register as Provider', function(done: any) { + // tslint:disable-next-line no-invalid-this + this.timeout(8000); + + const url = appConfig.startup_app.url; + const newUrl = url.slice(0, url.lastIndexOf('/')) + '/client.html'; + + const clientConfig : any = { + ...appConfig, + 'startup_app': { + 'name': 'Services', + 'description': 'services test app', + 'url': newUrl, + 'uuid': 'service-client-test', + 'autoShow': true, + 'saveWindowState': false, + 'nonPersistent': true, + 'experimental': { + 'v2Api': true + } + } + }; + + fs.writeFileSync(path.resolve('test/client.json'), JSON.stringify(clientConfig)); + + async function test () { + const spy = sinon.spy(); + const provider = await fin.Service.register(); + provider.register('test', () => { + spy(); + return 'return-test'; + }); + provider.onConnection(c => { + spy(); + }); + await launch({manifestUrl: path.resolve('test', 'client.json')}); + await fin.InterApplicationBus.subscribe({uuid: 'service-client-test'}, 'return', (msg: any) => { + assert(spy.calledTwice && msg === 'return-test', 'Did not get IAB from dispatch'); + done(); + }); + await delayPromise(1000); + await fin.InterApplicationBus.publish('start', 'hi'); + } + test(); + }); + + }); + + // tslint:disable-next-line + describe('External Client', function () { + + it('Should be able to connect as Client', function(done: any) { + // tslint:disable-next-line no-invalid-this + this.timeout(8000); + + const url = appConfig.startup_app.url; + const newUrl = url.slice(0, url.lastIndexOf('/')) + '/service.html'; + + const serviceConfig : any = { + ...appConfig, + 'startup_app': { + 'name': 'Service Provider', + 'description': 'Service Provider test app', + 'url': newUrl, + 'uuid': 'service-provider-test', + 'autoShow': true, + 'saveWindowState': false, + 'nonPersistent': true, + 'experimental': { + 'v2Api': true + } + } + }; + + fs.writeFileSync(path.resolve('test/service.json'), JSON.stringify(serviceConfig)); + + async function test() { + await launch({manifestUrl: path.resolve('test', 'service.json')}); + const client = await fin.Service.connect({uuid: 'service-provider-test'}); + client.dispatch('test').then(res => { + assert(res === 'return-test'); + done(); + }); + } + test(); + }); + }); +}); diff --git a/test/frame.test.ts b/test/frame.test.ts index d9e1ea36..87bb9e2c 100644 --- a/test/frame.test.ts +++ b/test/frame.test.ts @@ -1,13 +1,15 @@ import { conn } from './connect'; import * as assert from 'assert'; import { Fin, Frame } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('Frame.', () => { let fin: Fin; let testFrame: Frame; - before(() => { - return conn().then(a => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); beforeEach(() => { diff --git a/test/interappbus.test.ts b/test/interappbus.test.ts index b4367141..e6501f15 100644 --- a/test/interappbus.test.ts +++ b/test/interappbus.test.ts @@ -1,6 +1,7 @@ import { conn } from './connect'; import * as assert from 'assert'; import { connect as rawConnect, Fin } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; const id = 'adapter-test-window'; const topic = 'topic'; @@ -15,8 +16,12 @@ function noop() { } describe('InterApplicationBus.', () => { let fin: Fin; - beforeEach(() => { - return conn().then((a: Fin) => fin = a); + before(async () => { + await cleanOpenRuntimes(); + }); + + beforeEach(async () => { + fin = await conn(); }); it('subscribe()', (done) => { diff --git a/test/launcher.test.ts b/test/launcher.test.ts index 475dd19f..7775e820 100644 --- a/test/launcher.test.ts +++ b/test/launcher.test.ts @@ -4,7 +4,8 @@ import * as os from 'os'; import * as path from 'path'; import Launcher from '../src/launcher/launcher'; import { download, getRuntimePath, OsConfig, getUrl } from '../src/launcher/nix-launch'; -import { resolveRuntimeVersion, rmDir, promiseMap } from '../src/launcher/util'; +import { resolveRuntimeVersion, rmDir } from '../src/launcher/util'; +import { promiseMap } from '../src/util/promises'; describe('Launcher', () => { describe('Resolve Runtime', () => { diff --git a/test/multi-runtime-application.test.ts b/test/multi-runtime-application.test.ts index f9c72d21..97d69927 100644 --- a/test/multi-runtime-application.test.ts +++ b/test/multi-runtime-application.test.ts @@ -1,15 +1,19 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; -import { cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, launchX } from './multi-runtime-utils'; -import { clean } from './connect'; +import { cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, launchAndConnect } from './multi-runtime-utils'; -describe('Multi Runtime', () => { +describe('Multi Runtime', function () { let appConfigTemplate: any; - before(() => { - clean(); - }); + let fin: Fin; + + describe('Application', function () { - describe('Application', () => { + this.retries(2); + this.slow(TEST_TIMEOUT / 2 ); + this.timeout(TEST_TIMEOUT); function getAppConfig(): any { const appConfigTemplate = { @@ -22,27 +26,26 @@ describe('Multi Runtime', () => { } }; - // tslint:disable-next-line appConfigTemplate.uuid += Math.floor(Math.random() * 10000); return appConfigTemplate; } - beforeEach(() => { - appConfigTemplate = getAppConfig(); + before(async () => { + fin = await conn(); }); - afterEach(async () => { + beforeEach(async function () { + appConfigTemplate = getAppConfig(); return await cleanOpenRuntimes(); }); - describe('getInfo', () => { + describe('getInfo', function () { + it('should return the application Information', async function () { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); const expectedLaunchMode = 'adapter'; - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); + await delayPromise(DELAY_MS); + const realApp = await finB.Application.create(appConfigTemplate); await realApp.run(); const app = await finA.Application.wrap({ uuid: appConfigTemplate.uuid }); @@ -52,13 +55,10 @@ describe('Multi Runtime', () => { }); }); - describe('getParentUuid', () => { + describe('getParentUuid', function () { + it('should return the uuid of the parent adapter connection', async function () { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const expectedUuid = finB.wire.me.uuid; await delayPromise(DELAY_MS); @@ -72,13 +72,10 @@ describe('Multi Runtime', () => { }); }); - describe('isRunning', () => { + describe('isRunning', function () { + it('should return the running state of an application', async function () { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfigTemplate); diff --git a/test/multi-runtime-events.test.ts b/test/multi-runtime-events.test.ts index 88437041..11ee2626 100644 --- a/test/multi-runtime-events.test.ts +++ b/test/multi-runtime-events.test.ts @@ -1,8 +1,15 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; -import { launchAndConnect, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, launchX } from './multi-runtime-utils'; +import { launchAndConnect, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT } from './multi-runtime-utils'; -describe('Multi Runtime', () => { +describe('Multi Runtime', function() { + let fin: Fin; + + this.slow(TEST_TIMEOUT / 2 ); + this.timeout(TEST_TIMEOUT * 2); function getAppConfig() { const appConfigTemplate = { @@ -13,58 +20,55 @@ describe('Multi Runtime', () => { saveWindowState: false, accelerator: { devtools: true + }, + experimental: { + v2api: true } }; - // tslint:disable-next-line appConfigTemplate.uuid += Math.floor(Math.random() * 1000); return appConfigTemplate; } - afterEach(async () => { + before(async () => { + fin = await conn(); + }); + + beforeEach(async function() { return await cleanOpenRuntimes(); }); - describe('Events', () => { + describe('Events', function() { - describe('Launch then subscribe', () => { - describe('System', () => { - // tslint:disable-next-line - it('should raise application closed events', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT * 2); + describe('Launch then subscribe', function() { + describe('System', function() { + it('should raise application started events', function (done: Function) { async function test() { const appConfig = getAppConfig(); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); - const realApp = await finB.Application.create(appConfig); - await realApp.run(); + finA.System.on('application-started', async (e: any) => { + assert.equal(e.type, 'application-started', 'Expected event type to match event'); + await realApp.close(); - finA.System.on('application-closed', (e: any) => { - assert.equal(e.type, 'application-closed', 'Expected event type to match event'); done(); }); - await delayPromise(DELAY_MS); - await realApp.close(); - await delayPromise(1500); + + return await realApp.run(); } - test(); + test().catch(() => cleanOpenRuntimes()); }); it('should raise application created events', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT * 2); async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); + await delayPromise(DELAY_MS); + const appConfig = getAppConfig(); await delayPromise(DELAY_MS); let realApp: any; @@ -82,51 +86,42 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); }); - describe('Launch then subscribe', () => { - describe('application', () => { - // tslint:disable-next-line - it.skip('should raise closed events', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - + describe('Launch then subscribe', function() { + describe('Application', function() { + it('should raise application started events', function (done: Function) { async function test() { const appConfig = getAppConfig(); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); - - const realApp = await finB.Application.create(appConfig.uuid); - await realApp.run(); + const realApp = await finB.Application.create(appConfig); const app = await finA.Application.wrap({ uuid: appConfig.uuid }); + await delayPromise(DELAY_MS); + + app.on('started', async (e: any) => { + assert.equal(e.type, 'started', 'Expected event type to match event'); + await app.close(); - app.on('closed', (e: any) => { - assert.equal(e.type, 'closed', 'Expected event type to match event'); done(); }); await delayPromise(DELAY_MS); - await realApp.close(); - await delayPromise(DELAY_MS); + await realApp.run(); } - test(); + test().catch((err) => { + cleanOpenRuntimes(); + }); }); it('should raise initialized events', function (done: () => void) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; const appConfig = getAppConfig(); + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfig); @@ -143,24 +138,20 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); }); - describe('Launch then subscribe', () => { - describe('Window', () => { + describe('Launch then subscribe', function() { + describe('Window', function() { it('should raise initialized', function (done: (value: void) => void) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - async function test() { const appConfig = getAppConfig(); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); + const app = await finA.Application.wrap({ uuid: appConfig.uuid }); const win = await app.getWindow(); @@ -175,26 +166,24 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); }); - describe('Subscribe then launch', () => { + describe('Subscribe then launch', function() { - describe('System', () => { + describe('System', function() { - // tslint:disable-next-line - it('should raise application closed events', function (done: Function) { - // tslint:disable-next-line no-invalid-this + it('should raise application started events', function (done: Function) { this.timeout(TEST_TIMEOUT * 2); async function test() { const appConfig = getAppConfig(); const finA = await launchAndConnect(); await delayPromise(DELAY_MS); - finA.System.on('application-closed', (e: any) => { - assert.equal(e.type, 'application-closed', 'Expected event type to match event'); + finA.System.on('application-started', (e: any) => { + assert.equal(e.type, 'application-started', 'Expected event type to match event'); done(); }); const finB = await launchAndConnect(); @@ -207,22 +196,15 @@ describe('Multi Runtime', () => { await delayPromise(1500); } - test(); + test().catch(() => cleanOpenRuntimes()); }); - it('should raise application-started events', function (done: (value: void) => void) { - // tslint:disable-next-line no-invalid-this + it('should raise application-created events', function (done: (value: void) => void) { this.timeout(TEST_TIMEOUT * 2); //We need a bit more time for these tests. async function test() { const appConfig = getAppConfig(); - const argsConnect = [ - '--security-realm=supersecret', - '--enable-mesh', - '--enable-multi-runtime', - '--v=1' - ]; - const finA = await launchAndConnect(undefined, undefined, 'supersecret', argsConnect); + const finA = await launchAndConnect(); await delayPromise(DELAY_MS); const app = await finA.Application.wrap({ uuid: appConfig.uuid }); @@ -242,20 +224,17 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); }); - describe('Subscribe then launch', () => { + describe('Subscribe then launch', function() { - describe('Application', () => { + describe('Application', function() { - //Bug regarding Application/Window close events. - // tslint:disable-next-line - it.skip('should raise closed events', function (done: Function) { - // tslint:disable-next-line no-invalid-this + it('should raise started events', function (done: Function) { this.timeout(TEST_TIMEOUT * 2); async function test() { @@ -263,8 +242,9 @@ describe('Multi Runtime', () => { const finA = await launchAndConnect(); await delayPromise(DELAY_MS); const app = await finA.Application.wrap({ uuid: appConfig.uuid }); - app.on('closed', (e: any) => { - assert.equal(e.type, 'closed', 'Expected event type to match event'); + app.on('started', async (e: any) => { + assert.equal(e.type, 'started', 'Expected event type to match event'); + await app.close(); done(); }); const finB = await launchAndConnect(); @@ -273,25 +253,17 @@ describe('Multi Runtime', () => { await realApp.run(); await delayPromise(DELAY_MS); - await realApp.close(); - await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); - it('should raise initialized events', function (done: (value: void) => void) { - // tslint:disable-next-line no-invalid-this + it('should raise initialized eventsss', function (done: (value: void) => void) { this.timeout(TEST_TIMEOUT * 2); //We need a bit more time for these tests. async function test() { const appConfig = getAppConfig(); - const argsConnect = [ - '--enable-mesh', - '--enable-multi-runtime', - '--v=1' - ]; - const finA = await launchAndConnect(undefined, undefined, 'supersecret', argsConnect); + const finA = await launchAndConnect(); await delayPromise(DELAY_MS); const app = await finA.Application.wrap({ uuid: appConfig.uuid }); @@ -311,17 +283,16 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); }); - describe('Subscribe then launch', () => { - describe('Window', () => { + describe('Subscribe then launch', function() { + describe('Window', function() { it('should raise bounds-changed', function (done: (value: void) => void) { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT * 2); //We need a bit more time for these tests. async function test() { @@ -346,11 +317,10 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); it('should raise hidden', function (done: (value: void) => void) { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT * 2); //We need a bit more time for these tests. async function test() { @@ -359,6 +329,8 @@ describe('Multi Runtime', () => { const app = await finA.Application.wrap({ uuid: appConfig.uuid }); const win = await app.getWindow(); + await delayPromise(DELAY_MS); + win.on('hidden', (e: any) => { assert.equal(e.type, 'hidden', 'Expected event type to match event'); win.close().then(done); @@ -373,7 +345,7 @@ describe('Multi Runtime', () => { await delayPromise(DELAY_MS); } - test(); + test().catch(() => cleanOpenRuntimes()); }); }); diff --git a/test/multi-runtime-interappbus.test.ts b/test/multi-runtime-interappbus.test.ts index 4a44e243..ff6b55fb 100644 --- a/test/multi-runtime-interappbus.test.ts +++ b/test/multi-runtime-interappbus.test.ts @@ -1,22 +1,30 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; -import { cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, launchX } from './multi-runtime-utils'; +import { cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, launchAndConnect } from './multi-runtime-utils'; describe('Multi Runtime', function () { + let fin: Fin; - afterEach(async () => { + this.retries(2); + this.slow(TEST_TIMEOUT / 2 ); + this.timeout(TEST_TIMEOUT); + + before(async () => { + fin = await conn(); + }); + + beforeEach(async function () { return await cleanOpenRuntimes(); }); - describe('InterApplicationBus', () => { - it('should subscribe to * and publish', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + describe('InterApplicationBus', function () { + it('should subscribe to * and publish', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const data = 'hello'; @@ -27,22 +35,24 @@ describe('Multi Runtime', function () { assert.equal(data, message, 'Expected message to be the data sent'); done(); }); + await delayPromise(DELAY_MS); return await finB.InterApplicationBus.publish('my-topic', data); } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); - it('should subscribe to a uuid and publish', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + it('should subscribe to a uuid and publish', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const data = 'hello'; + + await delayPromise(DELAY_MS); + await finA.InterApplicationBus .subscribe({ uuid: finB.wire.me.uuid }, topic, (message: any, source: any) => { assert.equal(finB.wire.me.uuid, source.uuid, 'Expected source to be runtimeB'); @@ -53,20 +63,20 @@ describe('Multi Runtime', function () { await finB.InterApplicationBus.publish(topic, data); } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); - it('should subscribe to * and send', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + it('should subscribe to * and send', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const data = 'hello'; + await delayPromise(DELAY_MS); + await finA.InterApplicationBus.subscribe({ uuid: '*' }, topic, (message: any, source: any) => { assert.equal(finB.wire.me.uuid, source.uuid, 'Expected source to be runtimeB'); assert.equal(data, message, 'Expected message to be the data sent'); @@ -77,20 +87,20 @@ describe('Multi Runtime', function () { } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); - it('should subscribe to uuid and send', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + it('should subscribe to uuid and send', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const data = 'hello'; + await delayPromise(DELAY_MS); + await finA.InterApplicationBus.subscribe({ uuid: finB.wire.me.uuid }, topic, (message: any, source: any) => { assert.equal(finB.wire.me.uuid, source.uuid, 'Expected source to be runtimeB'); @@ -103,58 +113,58 @@ describe('Multi Runtime', function () { await finB.InterApplicationBus.send({ uuid: finA.wire.me.uuid }, topic, data); } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); - it('should get subscriberAdded Events', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + it('should get subscriberAdded Events', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const expectedUuid = finB.wire.me.uuid; + await delayPromise(DELAY_MS); + await finA.InterApplicationBus.on('subscriber-added', (sub: any, b: any) => { assert.equal(expectedUuid, sub.uuid, 'Expected UUIDs to match'); assert.equal(sub.topic, topic, 'Expected topics to match'); done(); }); await delayPromise(DELAY_MS); - // tslint:disable-next-line - return await finB.InterApplicationBus.subscribe({ uuid: finA.wire.me.uuid }, 'my-topic', () => { }); + return await finB.InterApplicationBus.subscribe({ uuid: finA.wire.me.uuid }, 'my-topic', function () { }); } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); - it('should get subscriberRemoved Events', function (done: Function) { - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); + it('should get subscriberRemoved Events', function (done: any) { async function test() { - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); const topic = 'my-topic'; const expectedUuid = finB.wire.me.uuid; + await delayPromise(DELAY_MS); + await finA.InterApplicationBus.on('subscriber-removed', (sub: any, b: any) => { assert.equal(expectedUuid, sub.uuid, 'Expected UUIDs to match'); assert.equal(sub.topic, topic, 'Expected topics to match'); done(); }); - // tslint:disable-next-line - function listener() { }; + function listener() { } await finB.InterApplicationBus.subscribe({ uuid: finA.wire.me.uuid }, topic, listener); await delayPromise(DELAY_MS); await finB.InterApplicationBus.unsubscribe({ uuid: finA.wire.me.uuid }, topic, listener); } - test(); + test().catch(err => { + cleanOpenRuntimes().then(done(err)); + }); }); }); diff --git a/test/multi-runtime-system.test.ts b/test/multi-runtime-system.test.ts index 78d636a3..3dec1ed3 100644 --- a/test/multi-runtime-system.test.ts +++ b/test/multi-runtime-system.test.ts @@ -1,8 +1,16 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; -import { launchX, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, getRuntimeProcessInfo } from './multi-runtime-utils'; +import { launchAndConnect, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT, getRuntimeProcessInfo } from './multi-runtime-utils'; -describe('Multi Runtime', () => { +describe('Multi Runtime', function () { + let fin: Fin; + + this.retries(2); + this.slow(TEST_TIMEOUT / 2 ); + this.timeout(TEST_TIMEOUT); function getAppConfig() { const appConfigTemplate = { @@ -16,31 +24,31 @@ describe('Multi Runtime', () => { } }; - // tslint:disable-next-line appConfigTemplate.uuid += Math.floor(Math.random() * 10000); return appConfigTemplate; } - afterEach(async () => { + before(async () => { + fin = await conn(); + }); + + beforeEach(async function () { return await cleanOpenRuntimes(); }); - describe('System', () => { + describe('System', function () { - describe('getAllApplications', () => { + describe('getAllApplications', function () { it('should return the application information from all runtimes', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); const appConfigA = getAppConfig(); const appConfigB = getAppConfig(); const appConfigC = getAppConfig(); const appConfigD = getAppConfig(); - const conns = await launchX(3); - // Why delay here? - await delayPromise(DELAY_MS); - const [finA, finB, finC] = conns; + const [finA, finB, finC] = await Promise.all([launchAndConnect(), launchAndConnect(), launchAndConnect()]); + await delayPromise(DELAY_MS); const [appA, appB, appC, appD] = await Promise.all([finA.Application.create(appConfigA), finB.Application.create(appConfigB), @@ -61,15 +69,14 @@ describe('Multi Runtime', () => { }); }); - describe('getAllExternalApplications', () => { + describe('getAllExternalApplications', function () { it('should return the external application information from all runtimes', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); - const conns = await launchX(3); + const conns = await Promise.all([launchAndConnect(), launchAndConnect(), launchAndConnect()]); + const finA = conns[0]; await delayPromise(DELAY_MS); - const [finA] = conns; const connStrings = conns.map(f => { const conn = getRuntimeProcessInfo(f); return `${conn.version}/${conn.port}/${conn.realm}`; @@ -87,21 +94,17 @@ describe('Multi Runtime', () => { }); }); - describe('getAllWindows', () => { + describe('getAllWindows', function () { it('should return the window information from all runtimes', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); const appConfigA = getAppConfig(); const appConfigB = getAppConfig(); const appConfigC = getAppConfig(); const appConfigD = getAppConfig(); - const conns = await launchX(3); - // Why delay here? + const [finA, finB, finC] = await Promise.all([launchAndConnect(), launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); - const [finA, finB, finC] = conns; - const [appA, appB, appC, appD] = await Promise.all([finA.Application.create(appConfigA), finB.Application.create(appConfigB), finB.Application.create(appConfigC), @@ -128,21 +131,17 @@ describe('Multi Runtime', () => { }); }); - describe('getProcessList', () => { + describe('getProcessList', function () { it('should return the process information from all runtimes', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); const appConfigA = getAppConfig(); const appConfigB = getAppConfig(); const appConfigC = getAppConfig(); const appConfigD = getAppConfig(); - const conns = await launchX(3); - // Why delay here? + const [finA, finB, finC] = await Promise.all([launchAndConnect(), launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); - const [finA, finB, finC] = conns; - const [appA, appB, appC, appD] = await Promise.all([finA.Application.create(appConfigA), finB.Application.create(appConfigB), finB.Application.create(appConfigC), diff --git a/test/multi-runtime-utils.ts b/test/multi-runtime-utils.ts index 166a1232..6d6a8505 100644 --- a/test/multi-runtime-utils.ts +++ b/test/multi-runtime-utils.ts @@ -1,3 +1,4 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ import * as https from 'https'; import * as os from 'os'; import * as rimraf from 'rimraf'; @@ -5,7 +6,8 @@ import * as fs from 'fs'; import * as path from 'path'; import * as ChildProcess from 'child_process'; import { connect as rawConnect, Fin } from '../src/main'; -import { resolveDir, first, serial, promiseMap } from '../src/launcher/util'; +import { resolveDir, first } from '../src/launcher/util'; +import { serial, promiseMap } from '../src/util/promises'; import { delayPromise } from './delay-promise'; const appConfig = JSON.parse(fs.readFileSync(path.resolve('test/app.json')).toString()); @@ -14,7 +16,8 @@ let uuidNum = 0; let runtimes: Array = []; -export const DELAY_MS = 100; +let ws_port = 8690; +export const DELAY_MS = 1000; export const TEST_TIMEOUT = 30 * 1000; export interface RuntimeProcess { @@ -23,22 +26,23 @@ export interface RuntimeProcess { port: string; version: string; fin?: Fin; + runtime: any; } async function spawnRealm(version: string, realm?: string, args?: Array): Promise { - // tslint:disable-next-line return new Promise((resolve, reject) => { - // tslint:disable-next-line no-function-expression resolveOpenFinVersion(version).then(async function(returnedVersion: string) { try { - // tslint:disable-next-line - const realm = `test_realm_${ Math.random() }`; - //const cacheDir = await realmCachePath(realm); + const realmArg = args && args.find(str => str.indexOf('security-realm') > -1); + const realmValue = realmArg && realmArg.split('=')[1]; + const realm = realmValue ? realmValue : `test_realm_${ Math.random() }`; const ofCacheFolder = path.resolve(process.env.LOCALAPPDATA, 'OpenFin', 'cache'); const cacheDir = path.resolve(ofCacheFolder, realm); const appConfig = generateAppConfig(); - const configLocation = path.resolve(cacheDir, `${appConfig.startup_app.uuid}.json`); + const configLocation = path.resolve(cacheDir, `${appConfig._startup_app.uuid}.json`); + // tslint:disable-next-line + const port = ++ws_port; args = args || [ '--enable-multi-runtime', @@ -51,36 +55,28 @@ async function spawnRealm(version: string, realm?: string, args?: Array) args.push(`--startup-url=${configLocation}`); fs.mkdirSync(cacheDir); - + appConfig.websocket_port = port; fs.writeFileSync(configLocation, JSON.stringify(appConfig)); const ofEXElocation = versionPath(returnedVersion); + const opts = { + env: { + ELECTRON_NO_ATTACH_CONSOLE: 1 + }, + detached: true - const runtime = ChildProcess.spawn(ofEXElocation, args); - - runtime.on('error', reject); - - // tslint:disable-next-line no-function-expression - const portSniffer = function(data: any) { - const sData = '' + data; - const matched = /^Opened on (\d+)/.exec(sData); - - if (matched && matched.length > 1 ) { - const port = matched[1]; - - runtime.stdout.removeListener('data', portSniffer); - - resolve({ - appConfig, - port, - runtime, - realm, - version: returnedVersion - }); - } }; + const runtime = ChildProcess.spawn(ofEXElocation, args, opts); - runtime.stdout.on('data', portSniffer); + await delayPromise(DELAY_MS); + + resolve({ + appConfig, + port, + runtime, + realm, + version: returnedVersion + }); } catch (e) { reject(e); @@ -119,18 +115,18 @@ function generateAppConfig(): any { return { uuid, - // tslint:disable-next-line - startup_app: { + _startup_app: { uuid, name: uuid, autoShow: true, url: appConfig.startup_app.url, - saveWindowState: false + saveWindowState: false, + experimental: appConfig.startup_app.experimental, + nonPersistent: true } }; } function resolveOpenFinVersion(version: string): Promise { - // tslint:disable-next-line return new Promise ((resolve, reject) => { // match point version eg. 6.29.17.14, fail on channels @@ -182,7 +178,6 @@ do taskkill /f /pid %a`; const cmd = `lsof -n -i4TCP:${port} | grep LISTEN | awk '{ print $2 }' | xargs kill`; ChildProcess.execSync(cmd); } - // tslint:disable-next-line:no-empty } catch (e) { } } @@ -191,8 +186,12 @@ export function kill(fin: Fin) { killByPort(getPort(fin)); } +export function killByruntime(runtimeProcess: RuntimeProcess) { + runtimeProcess.runtime.kill(); +} + async function closeAndClean(runtimeProcess: RuntimeProcess): Promise { - killByPort(runtimeProcess.port); + killByruntime(runtimeProcess); // give some time for rvm process to be killed await delayPromise(DELAY_MS); const cachePath = await realmCachePath(runtimeProcess.realm); @@ -200,7 +199,6 @@ async function closeAndClean(runtimeProcess: RuntimeProcess): Promise { } export async function launchAndConnect(version: string = process.env.OF_VER, - // tslint:disable-next-line uuid: string = `my-uuid ${appConfig.startup_app.uuid} ${Math.floor(Math.random() * 1000)}`, realm?: string, args?: Array): Promise { diff --git a/test/multi-runtime-window.test.ts b/test/multi-runtime-window.test.ts index 243b06e1..5415a72a 100644 --- a/test/multi-runtime-window.test.ts +++ b/test/multi-runtime-window.test.ts @@ -1,8 +1,17 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; -import { launchX, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT } from './multi-runtime-utils'; +import { launchAndConnect, cleanOpenRuntimes, DELAY_MS, TEST_TIMEOUT } from './multi-runtime-utils'; + +describe('Multi Runtime', function () { + let fin: Fin; + + this.retries(2); + this.slow(TEST_TIMEOUT / 2 ); + this.timeout(TEST_TIMEOUT); -describe('Multi Runtime', () => { let appConfigTemplate: any; function getAppConfig() { const appConfigTemplate = { @@ -16,28 +25,26 @@ describe('Multi Runtime', () => { } }; - // tslint:disable-next-line appConfigTemplate.uuid += Math.floor(Math.random() * 10000); return appConfigTemplate; } - beforeEach(() => { - appConfigTemplate = getAppConfig(); + before(async () => { + fin = await conn(); }); - afterEach(async () => { + + beforeEach(async function () { + appConfigTemplate = getAppConfig(); return await cleanOpenRuntimes(); }); - describe('Window', () => { + describe('Window', function () { - describe('moveBy', () => { + describe('moveBy', function () { it('should move the Window by the given values', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfigTemplate); await realApp.run(); @@ -53,20 +60,16 @@ describe('Multi Runtime', () => { }); }); - describe('resizeTo', () => { + describe('resizeTo', function () { it('should resize the Window by the given values', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); const resizeToVal = 200; - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfigTemplate); await realApp.run(); - const app = await finA.Application.wrap({ uuid: appConfigTemplate.uuid }); - const win = await app.getWindow(); + const win = await finA.Window.wrap({ uuid: appConfigTemplate.uuid, name: appConfigTemplate.uuid}); const bounds = await win.getBounds(); await win.resizeTo(resizeToVal, resizeToVal, 'top-left'); const postResizeBounds = await win.getBounds(); @@ -82,19 +85,15 @@ describe('Multi Runtime', () => { }); }); - describe('getState', () => { + describe('getState', function () { it('should return the state of the Window', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfigTemplate); await realApp.run(); - const app = await finA.Application.wrap({ uuid: appConfigTemplate.uuid }); - const win = await app.getWindow(); + const win = await finA.Window.wrap({ uuid: appConfigTemplate.uuid, name: appConfigTemplate.uuid }); const state = await win.getState(); const expectedState = 'normal'; @@ -105,12 +104,9 @@ describe('Multi Runtime', () => { }); it('should return the state of the Window post a minimize action', async function() { - // tslint:disable-next-line no-invalid-this this.timeout(TEST_TIMEOUT); - const conns = await launchX(2); - const finA = conns[0]; - const finB = conns[1]; + const [finA, finB] = await Promise.all([launchAndConnect(), launchAndConnect()]); await delayPromise(DELAY_MS); const realApp = await finB.Application.create(appConfigTemplate); await realApp.run(); diff --git a/test/multi-runtime.test.ts b/test/multi-runtime.test.ts index b0b34f3d..d4bb998c 100644 --- a/test/multi-runtime.test.ts +++ b/test/multi-runtime.test.ts @@ -1,11 +1,22 @@ +/* tslint:disable:no-invalid-this no-function-expression insecure-random mocha-no-side-effect-code no-empty */ +import { conn } from './connect'; +import { Fin } from '../src/main'; import * as assert from 'assert'; import { delayPromise } from './delay-promise'; import { cleanOpenRuntimes, DELAY_MS, getRuntimeProcessInfo, launchAndConnect, TEST_TIMEOUT } from './multi-runtime-utils'; -import { serial } from '../src/launcher/util'; -describe('Multi Runtime', () => { +describe('Multi Runtime', function() { + let fin: Fin; - afterEach(async () => { + this.retries(2); + this.slow(TEST_TIMEOUT); + this.timeout(TEST_TIMEOUT); + + before(async () => { + fin = await conn(); + }); + + beforeEach(async function() { return await cleanOpenRuntimes(); }); @@ -14,20 +25,17 @@ describe('Multi Runtime', () => { return `${version}/${port}/${realm ? realm : ''}`; } - describe('Connections', () => { + describe('Connections', function() { + it('should respect the enable-mesh flag for security realms', async function() { const argsConnect = [ - '--security-realm=superSecret' + `--security-realm=super-secret-${Math.floor(Math.random() * 1000)}` ]; - // tslint:disable-next-line no-invalid-this - this.timeout(TEST_TIMEOUT); - const conns = await serial([() => launchAndConnect(), - () => launchAndConnect(undefined, undefined, undefined, argsConnect), - () => launchAndConnect()]); - const finA = conns[0]; - const finB = conns[1]; - const finC = conns[2]; + const [ finA, finB, finC ] = await Promise.all([launchAndConnect(), + launchAndConnect(undefined, undefined, undefined, argsConnect), + launchAndConnect()]); + await delayPromise(DELAY_MS); const apps = await finA.System.getAllExternalApplications(); const uuidList = apps.map((a: any) => { return a.uuid; }); diff --git a/test/notification.test.ts b/test/notification.test.ts index 7f871263..991e384e 100644 --- a/test/notification.test.ts +++ b/test/notification.test.ts @@ -1,6 +1,7 @@ import { conn } from './connect'; import * as assert from 'assert'; import { Fin, Notification } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; // tslint:disable-next-line describe('Notification', function () { @@ -8,7 +9,8 @@ describe('Notification', function () { let notification: Notification; // tslint:disable-next-line this.timeout(30000); - before(() => { + before(async() => { + await cleanOpenRuntimes(); return conn().then(_fin => { fin = _fin; notification = fin.Notification.create({url: 'http://openfin.co'}); diff --git a/test/plugin.test.ts b/test/plugin.test.ts index 0e0ebf73..401f9e3c 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -1,22 +1,20 @@ import { conn } from './connect'; import { Fin } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('Plugin.', () => { let fin: Fin; - const plugin = { - name: 'plugin_1', - version: '0.0.1' - }; - before(() => { - return conn().then((res) => fin = res); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); describe('import()', () => { it('Doesn\'t work in Node environment', async () => { try { - await fin.Plugin.import(plugin); + await fin.Plugin.import('plugin_1'); } catch (error) { return true; } diff --git a/test/port-discovery.test.ts b/test/port-discovery.test.ts index 70b838b5..b2642449 100644 --- a/test/port-discovery.test.ts +++ b/test/port-discovery.test.ts @@ -3,11 +3,11 @@ import Launcher from '../src/launcher/launcher'; import * as assert from 'assert'; import * as fs from 'fs'; import { connect as rawConnect, launch } from '../src/main'; -import { promiseMap } from '../src/launcher/util'; +import { promiseMap } from '../src/util/promises'; import { ConnectConfig } from '../src/transport/wire'; import { kill, killByPort } from './multi-runtime-utils'; -import { clean } from './connect'; import { delayPromise } from './delay-promise'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; import * as path from 'path'; // tslint:disable-next-line const appConfig = JSON.parse(fs.readFileSync('test/app.json').toString()); @@ -16,7 +16,9 @@ describe.skip('PortDiscovery.', function () { // do NOT use => function here for 'this' to be set properly // tslint:disable-next-line this.timeout(60000); - before(clean); + before(async () => { + return await cleanOpenRuntimes(); + }); let spawns = 0; function makeConfig(config: any = {}): ConnectConfig { const defaultRconfig = { diff --git a/test/system.test.ts b/test/system.test.ts index bed1e6e8..4eab9d1f 100644 --- a/test/system.test.ts +++ b/test/system.test.ts @@ -1,14 +1,16 @@ import { conn } from './connect'; import { Fin } from '../src/main'; import * as assert from 'assert'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('System.', function () { let fin: Fin; // tslint:disable-next-line this.timeout(30000); - beforeEach(() => { - return conn().then((a: Fin) => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); describe('getVersion()', () => { diff --git a/test/window-event.test.ts b/test/window-event.test.ts index 0ac54c1e..f7a5967a 100644 --- a/test/window-event.test.ts +++ b/test/window-event.test.ts @@ -3,6 +3,7 @@ import { conn } from './connect'; import { delayPromise } from './delay-promise'; import * as assert from 'assert'; import { Fin } from '../src/main'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; // tslint:disable-next-line:no-function-expression describe('Window.', function() { @@ -21,8 +22,9 @@ describe('Window.', function() { } }; - before(() => { - return conn().then(a => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); describe('"closed"', () => { diff --git a/test/window.test.ts b/test/window.test.ts index a8db0632..c834fa04 100644 --- a/test/window.test.ts +++ b/test/window.test.ts @@ -2,6 +2,7 @@ import { conn } from './connect'; import * as assert from 'assert'; import { connect as rawConnect, Fin, Application, Window } from '../src/main'; import { delayPromise } from './delay-promise'; +import { cleanOpenRuntimes } from './multi-runtime-utils'; describe('Window.', function() { let fin: Fin; @@ -19,8 +20,9 @@ describe('Window.', function() { // tslint:disable-next-line this.timeout(30000); - before(() => { - return conn().then(a => fin = a); + before(async () => { + await cleanOpenRuntimes(); + fin = await conn(); }); beforeEach(() => { diff --git a/tutorials/Plugin.import.md b/tutorials/Plugin.import.md index febbdb80..1a2bbca5 100644 --- a/tutorials/Plugin.import.md +++ b/tutorials/Plugin.import.md @@ -1,16 +1,11 @@ -Imports an OpenFin plugin. Plugins can be written using ES modules, and the API object that +Imports an OpenFin plugin. Plugins can be written using ES modules, and the API object that is resolved in the promise contains the exported API of the plugin. ### Example ```js // This plugin must be listed in root application's manifest -const plugin = { - name: 'foo', - version: '0.0.1' -}; - -fin.desktop.Plugin.import(plugin) +fin.desktop.Plugin.import('foo') .then((api) => { api.bar(); })