diff --git a/.gitignore b/.gitignore index baf93a1..240c479 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,6 @@ build/ # Visual Studio Code .vscode + + +.idea/ \ No newline at end of file diff --git a/packages/api-specification/api/application.ts b/packages/api-specification/api/application.ts new file mode 100644 index 0000000..a7cb4cf --- /dev/null +++ b/packages/api-specification/api/application.ts @@ -0,0 +1,33 @@ +export interface ApplicationAPI { + /** + * Returns an array containing the command line arguments passed when application was launched + */ + argv: string[]; + + /** + * Application configuration object (as defined in app.json file) + */ + config: ApplicationConfig; + + /** + * The current value displayed in the counter badge. + */ + getBadgeCount(): number; + + /** + * Sets the counter badge for current app. Setting the count to 0 will hide the badge. + */ + setBadgeCount(count: number); + + /** + * Shutdown the application + */ + shutdown: () => void; +} + +export interface ApplicationConfig { + name: string; + url: string; + uuid: string; + autoShow: boolean; +} \ No newline at end of file diff --git a/packages/api-specification/api/common.ts b/packages/api-specification/api/common.ts new file mode 100644 index 0000000..e65ae53 --- /dev/null +++ b/packages/api-specification/api/common.ts @@ -0,0 +1,20 @@ +// contains common types +export interface Bounds { + top: number; + left: number; + width: number; + height: number; +} + +export interface Size{ + width: number; + height: number; +} + +/** + * Function returned from event subscription functions (e.g. onWindowBoundsChanged). + * Executing it will remove the event handler. + */ +export interface UnsubscribeFunction { + (): void; +} \ No newline at end of file diff --git a/packages/api-specification/api/confluence.md b/packages/api-specification/api/confluence.md new file mode 100644 index 0000000..a9dd0a7 --- /dev/null +++ b/packages/api-specification/api/confluence.md @@ -0,0 +1,111 @@ +## [Activate API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Activate+API) + +> This api allows a window to be move to the front and given focus. This API is needed since JS web apps have no capability to perform such a function. +> An example use case might be: the main window might be minimized (or otherwise hidden) and the notification shows that the user then clicks on. The web app needs to ability to bring the window to the foreground and give focus. + +The API proposal introduces window management section that enables the user to create, discover and manage windows. +It also defines a window object that has a set of properties and methods. + +One of these methods is activate that will bring the window to front and focus it: +```javascript +// activate my window +var myWindow = ssf.windows.current; +myWindow.activate(); + +// activate some other window +var someWindow = ssf.windows.getById('4'); +someWindow.activate(); +``` + +## [RegisterBoundsChange API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/RegisterBoundsChange+API) +> This API allows JS web app to register a callback that will be notified whenever any child window's size or position changes. The primary motivation is to support a feature called "saved layout". This feature allows the symphony web app to save the size and location of child windows and restore them when the app is restarted (or refreshed). + +Using the windows API the application can subscribe for +```javascript +ssf.windows.onWindowBoundsChanged(function(window, bounds){ + +}) +``` + +or subscribe for each window individually +```javascript +someWindow.on('boundsChanged', function(bounds){ + +}); +``` + +or iterate the windows when the layout should be saved +```javascript +ssf.windows.all.forEach(function(window){ + var boundsToSave = window.getBounds(); +}); +``` +## [Activity API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Activity+API) +> The application needs to be informed if activity (keyboard or mouse input) has occurred. + +Another section in the proposal is system API that allows the application to access system specific stuff. + +The application can subscribe for user activity events: +```javascript +ssf.system.onUserActivity(function(){ + +}, 1000); +``` + +## [getMediaSources API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/getMediaSources+API) and [ScreenSnippet API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/ScreenSnippet+API) +> In order to support screen sharing the client api needs enumerate screens and windows. +> This api provided a list of screens (each monitor) and list of windows available; providing title, id and thumbnail. This api is essentially equivalent of electron api: https://electron.atom.io/docs/api/desktop-capturer/#desktopcapturergetsourcesoptions-callback + +> The ScreenSnippet API is used to capture a snippet of their desktop (unconstrained by the host application window) and highlight portions of this snippet so it can then be consumed by the host application. This functionality is similar to the Windows Screen Snippet tool when used in rectangle capture mode. This lets a user captures portions of the Windows Desktop, highlight aspects of the image and then save this image for sharing. +Using the system API the user can enumerate the displays and capture any of them +```javascript +// capture all screens +ssf.system.capture().then(function(image){ + +}); + +// capture each screen as separate image +ssf.system.captureAllDisplays().then(function(images) { + +}); + +// capture specific display only +ssf.system.displays[0].capture(); + +// Capture all windows as separate images with custom size +ssf.system.captureAllWindows({imageSize: {width: 100, height: 100}}) + .then(function(images){ + + }); + +// Capture specific window (this goes throught the window API) +var window = ssf.windows.current; +window.capture(); +``` + +## [BadgeCount API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/BadgeCount+API) +> Display a number on the application icon in the desktop tray... usually to indicate number of unread messages for given application. + +There is application section that provides information about application config and allows +interactions with app specific functionalities like badgeCounts + +```javascript +ssf.application.setBadgeCount(11); +``` +## [Notification API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Notification+API) +>The Notifications API is used to configure and display desktop notifications to the user. It is exposed as an extension to the HTML5 Notification API, providing additional functionality that is useful for financial desktop applications. +> +>The exact visual style and the extent of the OS-level integration is container dependent and hence out-of-scope of this specification. + +The notification API that we propose is following the HTML5 notifications standard (https://developer.mozilla.org/en-US/docs/Web/API/notification) + +If we need any extensions these can be added on top of it. + +## [Version API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Version+API) +> This API allows the JS web app to interrogate the container to find version support information. One possible use case is to get ssf API version supported so that in future it is possible to deprecate older containers that do not support latest version of API. Another possible use case is for logging purposes to help support track down issues. + +There is an info section in the API that provides the api version and also container information (version, capabilities, etc) + +```javascript +var apiVersion = ssf.info.apiVersion; +``` \ No newline at end of file diff --git a/packages/api-specification/api/info.ts b/packages/api-specification/api/info.ts new file mode 100644 index 0000000..011cb68 --- /dev/null +++ b/packages/api-specification/api/info.ts @@ -0,0 +1,22 @@ +export interface SSFInfo{ + /** Version of the API **/ + apiVersion: string; + + /** Container information **/ + container: ContainerInfo; +} + +export interface ContainerInfo { + /** Name of the container */ + name: string; + + /** Version of the container */ + version: string; + + /** + * Capabilities supported by the container. + * Expressed as object where each property is a capability and the value + * is the level of support in the container + */ + capabilities: {[key:string]: string}; +} \ No newline at end of file diff --git a/packages/api-specification/api/messages.ts b/packages/api-specification/api/messages.ts new file mode 100644 index 0000000..3fd723f --- /dev/null +++ b/packages/api-specification/api/messages.ts @@ -0,0 +1,10 @@ +/** + * Currently this is based on OpenFin InterApplicationBus - http://cdn.openfin.co/jsdocs/stable/fin.desktop.InterApplicationBus.html + * + * TBD - Request/response and streaming + */ +export interface MessagesAPI{ + send(windowId: string, topic :string, message: string|object): void; + subscribe(windowId: string, topic :string, listener: Function): void; + unsubscribe(windowId: string, topic :string, listener: Function): void; +} \ No newline at end of file diff --git a/packages/api-specification/api/notifications.ts b/packages/api-specification/api/notifications.ts new file mode 100644 index 0000000..ecd4dea --- /dev/null +++ b/packages/api-specification/api/notifications.ts @@ -0,0 +1,70 @@ +/** + * Currently following HTML5 notification specs + * + * Imported from typescript definition files + * https://github.com/Microsoft/TSJS-lib-generator/blob/cd60588b72a9188e89346b3c440a76508b4c0e76/baselines/dom.generated.d.ts#L8360-L8381 + */ +export interface NotificationsAPI { + permission: NotificationPermission; + maxActions: number; + + requestPermission(): Promise; +} + +export type NotificationPermission = "default" | "denied" | "granted"; + +export type NotificationDirection = "auto" | "ltr" | "rtl"; + +export interface NotificationPermissionCallback { + (permission: NotificationPermission): void; +} + +export interface NotificationOptions { + dir?: NotificationDirection; + lang?: string; + body?: string; + tag?: string; + image?: string; + icon?: string; + badge?: string; + sound?: string; + vibrate?: number | number[], + timestamp?: number, + renotify?: boolean; + silent?: boolean; + requireInteraction?: boolean; + data?: any; + actions?: NotificationAction[] +} + +export interface NotificationAction { + action: string; + title: string; + icon?: string; +} + +declare class Notification extends EventTarget { + constructor(title: string, options?: NotificationOptions); + + onclick: EventListenerOrEventListenerObject; + onerror: EventListenerOrEventListenerObject; + + close(): void; + + readonly title: string; + readonly dir: NotificationDirection; + readonly lang: string; + readonly body: string; + readonly tag: string; + readonly image: string; + readonly icon: string; + readonly badge: string; + readonly sound: string; + readonly vibrate: number[]; + readonly timestamp: number; + readonly renotify: boolean; + readonly silent: boolean; + readonly requireInteraction: boolean; + readonly data: any; + readonly actions: NotificationAction[] +} \ No newline at end of file diff --git a/packages/api-specification/api/readme.md b/packages/api-specification/api/readme.md new file mode 100644 index 0000000..fb63869 --- /dev/null +++ b/packages/api-specification/api/readme.md @@ -0,0 +1,9 @@ +This is an attempt to define an API that tries to unify what we currently have in ContainerJS, [the working group APIs](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Proposed+Standard+API+Specifications) +and things that we at Tick42 think are useful based on our experience. + +This is an initial version that aims to start a discussion - some parts of it are not fully defined (e.g. window events) and some +upcoming parts are just mentioned in comments (e.g. interop). + +This also contains a [document](./confluence.md) describing how to map the [confluence API](https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/Proposed+Standard+API+Specifications) to the current API. + +The starting point when reading the code should be [ssf.ts](./ssf.ts) file that describes the ssf root object as exposed to clients applications. \ No newline at end of file diff --git a/packages/api-specification/api/ssf.ts b/packages/api-specification/api/ssf.ts new file mode 100644 index 0000000..8969bd9 --- /dev/null +++ b/packages/api-specification/api/ssf.ts @@ -0,0 +1,46 @@ +import {SystemAPI} from "./system"; +import {WindowsAPI} from "./windows"; +import {MessagesAPI} from "./messages"; +import {NotificationsAPI} from "./notifications"; +import {ApplicationAPI} from "./application"; +import {SSFInfo} from "./info"; + +/** + * Describes the ssf object as exposed to clients applications + * + * SSF object groups different APIs, where specific API methods can be accessed using the corresponding sub-object, e.g. + * to access window management API : + * + * ssf.windows.open({name:'search', url: 'http://google.com'}); + */ +export interface SSF { + /** + * Window management API - create, discover and manage windows + */ + windows: WindowsAPI; + + /** + * Messaging API - communicate with other windows + */ + messages: MessagesAPI; + + /** + * Notifications API - publish notifications to the user + */ + notifications: NotificationsAPI; + + /** + * Application API - access application specific information + */ + application: ApplicationAPI; + + /** + * System API - access system specific stuff,e.g. display information, user activity, os information... + */ + system : SystemAPI; + + /** + * Info API - general info about the API and the host container + */ + info: SSFInfo; +} diff --git a/packages/api-specification/api/system.ts b/packages/api-specification/api/system.ts new file mode 100644 index 0000000..ba37f8e --- /dev/null +++ b/packages/api-specification/api/system.ts @@ -0,0 +1,88 @@ +import {Bounds, Size} from "./common"; + +export interface SystemAPI { + /** + * Info about all displays available + */ + displays: () => Display[]; + + /** + * Capture + * @param options + */ + capture: (options?: CaptureOptions) => Promise; + + /** + * Capture specific display + * @param displayId + * @param options + */ + captureDisplay: (displayId: number, options?: CaptureOptions) => Promise; + + captureAllDisplays: (options?: CaptureOptions) => Promise; + + /** + * Capture all windows in a separate images + * @param options + */ + captureAllWindows: (options?: CaptureOptions) => Promise; + + /** + * Whenever user activity occurs (keyboard or mouse input) the callback will be invoked. + * If additional activity occurs within the period given by the 'throttle', then the callback will be + * invoked at time: X + throttle (where X is the time of last activity report send). + */ + onUserActivity(callback: ()=> void, throtle: number); + + /** + * Operating system information + */ + os: OperatingSystemInfo; +} + +export interface Display { + /** Unique identifier associated with the display. */ + id: number; + + /** Can be 0, 90, 180, 270, represents screen rotation in clock-wise degrees. */ + rotation: number; + + /** Output device's pixel scale factor. */ + scaleFactor: number; + + /** True if this is the primary display */ + primary: boolean; + + /** Bounds of the display **/ + bounds: Bounds; + + /** Working area of the display */ + workingArea: Bounds; + + capture(): Promise; +} + +export interface OperatingSystemInfo{ + platform: 'Windows' | 'OSX' | 'Linux'; + version: string; + is64bit: boolean; +} + +export interface CaptureOptions { + imageSize: Size; +} + +export interface DisplayImageData extends Base64ImageData{ + display: Display; +} + +export interface WindowImageData extends Base64ImageData { + window: Window; +} + +export interface Base64ImageData { + type: string; + data: string; + width: number; + height: number; +} \ No newline at end of file diff --git a/packages/api-specification/api/windows.ts b/packages/api-specification/api/windows.ts new file mode 100644 index 0000000..73bb9f6 --- /dev/null +++ b/packages/api-specification/api/windows.ts @@ -0,0 +1,313 @@ +import {Bounds, UnsubscribeFunction} from "./common"; +import {Base64ImageData, CaptureOptions} from "./system"; +import EventEmitter = NodeJS.EventEmitter; + +export interface WindowsAPI { + + /** Returns the current window */ + current: Window; + + /** Returns all windows in the application */ + all: Window[]; + + /** + * The window that is focused in this application, otherwise returns null + */ + focusedWindow: Window; + + /** + getById(windowId: string): Window; + + /** Opens a new window */ + open(options: WindowOptions): Promise; + + /** + * Receive notifications when a new window is opened + * @returns Function Execute the function to unsubscribe + */ + onWindowOpened(callback: (window: Window) => void): UnsubscribeFunction; + + /** + * Receive notifications when a window was closed + * @returns Function Execute the function to unsubscribe + */ + onWindowClosed(callback: (window: Window) => void): UnsubscribeFunction; + + /** + * Subscribe for bounds changed event for all windows + * @returns Function Execute the function to unsubscribe + */ + onWindowBoundsChanged(callback: (window: Window, bounds: Bounds) => void): UnsubscribeFunction; +} + +/** Window options used to open a new window **/ +export interface WindowOptions { + /** + * Default window title. + */ + name: string; + + /** + * URL that this window loads. + */ + url: string; + + /** + * Window initial bounds. Can contain partial bounds (e.g width only) and be + * combined with center + */ + bounds?: Bounds; + + /** + * Window’s maximum width. + */ + maxWidth?: number; + + /** + * Window’s minimum width. + */ + minWidth?: number; + + /** + * Window’s maximum height. + */ + maxHeight?: number; + + /** + * Window’s minimum height. + */ + minHeight?: number; + + /** + * Whether the window should always stay on top of other windows. Default is false. + */ + alwaysOnTop?: boolean; + + /** + * Window’s background color as Hexadecimal value. + */ + backgroundColor?: string; + + /** + * Whether the window is a child of the current window. Default is false. + */ + child?: boolean; + + /** + * Show window in the center of the screen. + */ + center?: boolean; + + /** + * If false, creates a frameless window. Default is true. + */ + frame?: boolean; + + /** + * Whether window should have a shadow. This is only implemented on macOS. Default is true. + */ + hasShadow?: boolean; + + /** + * Whether window is maximizable. Default is true. + */ + maximizable?: boolean; + + /** + * Whether window is minimizable. Default is true. + */ + minimizable?: boolean; + + /** + * Whether window is resizable. Default is true. + */ + resizable?: boolean + + /** + * Whether window should be shown when created. Default is true. + */ + show?: boolean; + + /** + * Whether to show the window in taskbar. Default is false. + */ + skipTaskbar?: boolean; + + /** + * Makes the window transparent. Default is false. + */ + transparent?: boolean; +} + +/** + * A window running in the container. + * It implements EventEmitter which means users can subscribe for events using on, once , addEventListener etc. + * The list of events is in WindowsEvents structure; + */ +export interface Window extends EventEmitter { + + /** Get or set the max width of the window. Might be undefined if no restrictions applied**/ + maxWidth?: number; + + /** Get or set the min width of the window. Might be undefined if no restrictions applied**/ + minWidth?: number; + + /** Get or set the max height of the window. Might be undefined if no restrictions applied**/ + maxHeight?: number; + + /** Get or set the min height of the window. Might be undefined if no restrictions applied**/ + minHeight?: number; + + /** + * Get or set if the window has a shadow. + */ + hasShadow: boolean; + + /** + * Get or set the title of the window + */ + title: string; + + /** + * Get or set if the window is always on top of all other windows. + */ + alwaysOnTop: boolean; + + /** + * Get or set if the window can be maximized. + */ + maximizable: boolean; + + /** + * Get or set if the window can be minimized. + */ + minimizable: boolean; + + /** + * Get or set if the window can be re-sized. + */ + resizable: boolean; + + /** + * Get or set if the window is shown in the taskbar. + */ + skipTaskbar: boolean; + + /** + * Removes focus from the window. + * @returns {Promise} A promise which resolves to nothing when the function has completed. + */ + blur(): Promise; + + /** + * Closes the window. + * @returns {Promise} A promise which resolves to nothing when the function has completed. + */ + close(): Promise; + + /** + * Flashes the window's frame and taskbar icon. + * @param {boolean} flag - Flag to start or stop the window flashing. + * @returns {Promise} A promise which resolves to nothing when the function has completed. + */ + flashFrame(flag: boolean): Promise; + + /** + * Activates the window. This will bring the window to front or restore it if minimized. + * If focus flag is true (default) the window will also receive input focus. + * @returns {Promise} A promise which resolves to nothing when the function has completed. + */ + activate(focus?: boolean): Promise; + + /** + * Captures an image of the window + * @param options + */ + capture(options?: CaptureOptions): Promise; + + /** Get the bounds of the window */ + getBounds(): Bounds; + + /** + * Get the child windows of the window. + */ + getChildWindows(): Window[]; + + /** + * Get the parent of the window. Null will be returned if the window has no parent. + */ + getParentWindow(): Window; + + /** + * Returns the current state of the window - normal, maximized or minimized + */ + getState(): "normal" | "maximized" | "minimized"; + + /** + * Hides the window. + * @returns {Promise} A promise that resolves to nothing when the window has hidden. + */ + hide(): Promise; + + /** + * Load a new URL in the window. + * @param {string} url - The URL to load in the window. + * @returns {Promise} A promise that resolves when the window method succeeds. + */ + loadURL(url: string): Promise; + + /** + * Reload the window. + * @returns {Promise} A promise that resolves when the window method succeeds. + */ + reload(): Promise; + + /** + * Restores the window to normal state. + * @returns {Promise} A promise that resolves to nothing when the window method succeeds. + */ + restore(): Promise; + + /** Set new bounds */ + setBounds(bounds: Bounds); + + /** + * Sets the window icon. + * @param {string} icon - The url to the image. + * @returns {Promise} A promise that resolves to nothing when the option has been set. + */ + setIcon(icon: string): Promise; + + /** + * Show the window. + * @returns {Promise} A promise that resolves to nothing when the window is showing. + */ + show(): Promise; + + /** + * Maximize the window. + * @returns {Promise} A promise that resolves to nothing when the window has maximized. + */ + maximize(): Promise; + + /** + * Minimize the window. + * @returns {Promise} A promise that resolves to nothing when the window has minimized. + */ + minimize(): Promise; + + /** + * Send a message to the window. + * @param {string|object} message - The message to send to the window. Can be any serializable object. + */ + sendMessage(message: string | object): Promise; +} + +/** To be extended **/ +export type WindowEvents = + 'moved'| + 'resized'| + 'closed'| + 'focused' | + 'maximized' | + 'minimized'; \ No newline at end of file