Skip to content
This repository has been archived by the owner on Sep 7, 2018. It is now read-only.

Feature/api definitions #171

Open
wants to merge 24 commits into
base: master
Choose a base branch
from

Conversation

kirilpopov
Copy link

@kirilpopov kirilpopov commented May 31, 2017

This is an attempt to define an API to unify what we currently have in ContainerJS, the working group APIs
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 describing how to map the [the working group APIs in confluence (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 file that describes the ssf root object as exposed to clients applications.

Copy link
Collaborator

@BenLambertNcl BenLambertNcl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a great start!

I have left a few comments, mostly high level, but i'm not sure how much leeway we have in changing some of these APIs. I will do another code review once we have finalized on the design :-).

capture(options?: CaptureOptions): Promise<Base64ImageData>;

/** Get the bounds of the window */
getBounds(): Bounds;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all window methods should be asynchronous, as OpenFin methods are all async.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are, although I do wonder whether we should wrap them to be synchronous? All the equivalent Electron methods are synchronous.

I wonder whether this is an OpenFin implementation detail that has 'leaked' into the public API?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually these are sync operations , not sure why openfin did it that way.
Also I think usage is better

//move my window with 10px
window.setBounds({left: window.getBounds().left + 20});

//vs
window.getBounds().then((bounds)=>{
 window.setBounds({left: bounds.left + 20});
});

export interface Window extends EventEmitter {

/** Get or set the max width of the window. Might be undefined if no restrictions applied**/
maxWidth?: number;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these will need to be getters/setters as they can change after the window is open. OpenFin's methods are also async, see below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes these can be implemented as properties with getter and setters. (I prefer this over get* set* methods),

window.maxWidth = 10;
console.log(window.maxWidth);

What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it would work, unless we have them as read only and use them instead of get methods. If we had an open window and did window.maxWidth = 10;, then we need a way of calling the underlying method to set the value. I think the only way to do this would be to watch for variable changes? Unless you can think of another way? :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of using properties, something like (in typescript)

class Window{
    private _maxWidth: number;

    get maxWidth():boolean {
        return this._maxWidth;
    }
    set maxWidth(width:number) {
        // do some container specific magic
        this._maxWidth= width;
    }
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah I forgot we were using TypeScript 😄. Sounds like a good idea to me.

@@ -0,0 +1,20 @@
// contains common types
export interface Bounds {
top: number;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Electron, top and left are x and y. If we are trying to stick to the Electron API as much as possible then maybe this should be changed?

* 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<void>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTML5 API is postMessage() so we should maybe use that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok will update

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

getById(windowId: string): Window;

/** Opens a new window */
open(options: WindowOptions): Promise<Window>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using the constructor pattern that is used in some of the newer browser APIs (like notifications). Might be better to keep it like this though if window.open is used in the symphony application.

export interface WindowsAPI {

/** Returns the current window */
current: Window;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have getById(), then we might want to have these as getCurrent() and getFocusedWindow() etc. this would match both Electron + OpenFinas well.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes these should be get* methods . Will update

@@ -0,0 +1,33 @@
export interface ApplicationAPI {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to clarify the meaning of Application for OpenFin, as currently OpenFin windows that are not child windows are created using fin.desktop.Application(). Would this API be used for each OpenFin application, or ContainerJS application? If we want this per ContainerJS application, then we might need to rethink the setBadgeCount() or the window creation implementation as each ContainerJS application could have multiple top level windows, which could have different badge counts.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a wider question is, should the API support multiple applications?

With OpenFin you can create a new Application instance through the JavaScript API.

Personally I think this is a bit of an edge case. I can't see when you'd want to do this in practice? I'd be happy with ContainerJS treating the Application as a singleton.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that.
We should decide if we want to have access to other applications and be able to start some of them (however I'm not sure how other apps will be discovered in Electron case)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep it as is for the time being, and have this as a single ContainerJS application. We can always change it in the future if we hit any problems

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general need to think about which aspects are mandatory and optional

/**
* Notifications API - publish notifications to the user
*/
notifications: NotificationsAPI;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the interesting thing here is that both within the browser and Electron container users will see two notification APIs:

new Notification(...)
new ssf.Notification(...)

This is probably unavoidable, but something that users need to be warned about.

* 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throttle

Copy link
Author

@kirilpopov kirilpopov Jun 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks will change

capture(options?: CaptureOptions): Promise<Base64ImageData>;

/** Get the bounds of the window */
getBounds(): Bounds;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are, although I do wonder whether we should wrap them to be synchronous? All the equivalent Electron methods are synchronous.

I wonder whether this is an OpenFin implementation detail that has 'leaked' into the public API?

* Receive notifications when a new window is opened
* @returns Function Execute the function to unsubscribe
*/
onWindowOpened(callback: (window: Window) => void): UnsubscribeFunction;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why Window is an event emitter, whereas the WindowsAPI has explicit methods for each event type?

I'd prefer to stick to the event emitter pattern!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok will change

focusedWindow: Window;

/**
getById(windowId: string): Window;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing comment

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks - will update


declare class Notification extends EventTarget {
constructor(title: string, options?: NotificationOptions);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like missing requestPermission method.


close(): void;

readonly title: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also need likely need a sticky option

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a standard field?

close(): void;

readonly title: string;
readonly dir: NotificationDirection;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to think how we might handle non-std fields. e.g., blink and blink color.

* 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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one issue i have here is that is kind of re-inventing the wheel. javascript already has a Window object and window.open nicely returns one these. so for javascript already using window.open this might require significant refactoring and might not be able to achieve same functionality. for example, window.opener. here the equivalent looks like window. getParentWindow() but it still don't think will give same functionality. what about postMesage and 'closed'

for example, one use case we have is we want child window to able to share large object (redux state) with parent. with window.opener this is possible. not sure here.

is it possible to augment existing window object (or inherit from it) and add missing/optional things we need here???

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about reinventing but for defining a common contract for container window. Note that this is not just about the current window, it's a way to address any window in your application from the current one (even if they don't have a parent/child relationship).

Any container I've worked with (Electron, OpenFin, GlueDesktop (which is Tick42 CEF based container)) has similar contract for windows, because the standard browser windowing API is not good enough for desktop applications.

Augmenting the existing window is actually a proposal for implementation of this contract.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for example, one use case we have is we want child window to able to share large object (redux state) with parent. with window.opener this is possible. not sure here.

One problem you'll encounter here is that the process model differs between various containers / browser.

Within a browser, a window opened via window.open is in the same process, therefore window.opener from the child process uses the same JavaScript context, and as a result, sharing objects, such as redux state becomes possible.

This is also true within OpenFin.

However, in Electron each renderer window has its own process, so you cannot just use window.opener to access the parent context and state.

This is why projects such as https://github.com/samiskin/redux-electron-store exist.

Within ContainerJS we are looking to create a consistent process model across all containers, as well as a consistent API.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using window.open (with sandbox on) in electron like this works just fine, the child can use the window.opener. we have this working currently in electron. when window.open url has the same domain, then it runs in the same process and the child can access the parent.

but yes if in diff process then window.opener won't work without some extra method to proxy between process across ipc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we recently discovered this behaviour, and @BenLambertNcl is exploring it further via #195

* Capture
* @param options
*/
capture: (options?: CaptureOptions) => Promise<Base64ImageData>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this capture working? would like to see something similar to ScreenSnippet here https://symphonyoss.atlassian.net/wiki/display/WGDWAPI/ScreenSnippet+API


export interface SystemAPI {
/**
* Info about all displays available
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* TBD - Request/response and streaming
*/
export interface MessagesAPI{
send(windowId: string, topic :string, message: string|object): void;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is windowId here?

* 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<void>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how to receive a message? ??? win.addEventListener('message', callback);

/**
* Messaging API - communicate with other windows
*/
messages: MessagesAPI;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this diff than window.sendMessage (postMesage) below?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants