Skip to content

Commit

Permalink
docs: for NodeConnector API docs
Browse files Browse the repository at this point in the history
  • Loading branch information
abose committed Dec 15, 2023
1 parent 0804352 commit bb77703
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/generatedApiDocs/GitHub-API-Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The list of all APIs for phoenix.
1. [features/NewFileContentManager](NewFileContentManager-API)
1. [features/QuickViewManager](QuickViewManager-API)
1. [features/SelectionViewManager](SelectionViewManager-API)
1. [NodeConnector](NodeConnector-API)
1. [utils/EventDispatcher](EventDispatcher-API)
1. [utils/EventManager](EventManager-API)
1. [utils/ExtensionInterface](ExtensionInterface-API)
Expand Down
155 changes: 155 additions & 0 deletions docs/generatedApiDocs/NodeConnector-API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

## NodeConnector

Use this module to communicate easily with node and vice versa. A `NodeConnector` is an intermediary between a
module in node and a module in phcode. With a `NodeConnector` interface, you can execute a function in node
from phoenix by simply calling `await nodeConnectorObject.execPeer("namedFunctionInNodeModule, argObject")` and
the resolved promise will have the result. No need to do any complex IPC or anything else. See a step by step
example below on how this is done.

## The setup

Assume that you have a module in phcode `x.js` and another module in node `y.js`. To communicate between `x` and `y`
we have to first create a `NodeConnector` on both sides. A `NodeConnector` will have a unique ID that will be
same in both sides. In this example, lets set the id as `ext_x_y` where `ext` is your extension id to prevent
name collision with other extensions. Phoenix core reserves the prefix `ph_` for internal use.

### Create `NodeConnector` in Phoenix side `x.js`

```js
const NodeConnector = require('NodeConnector');
const XY_NODE_CONNECTOR_ID = 'ext_x_y';
let nodeConnector;
const nodeConnectedPromise = NodeConnector.createNodeConnector(XY_NODE_CONNECTOR_ID, exports).then(connector=>{
nodeConnector = connector;
});

exports.modifyImage = async functions(imageName, imageArrayBugger){
// do some image ops with the imageArrayBugger
// to return an arry buffer, you should return an object that contains a key `buffer` with the `ArrayBuffer` contents.
return {
operationDone: "colored,cropped",
buffer: imageArrayBugger
};
};
```

### Create `NodeConnector` in Node `y.js`

```js
const XY_NODE_CONNECTOR_ID = 'ext_x_y';
let nodeConnector;
const nodeConnectedPromise = global.createNodeConnector(XY_NODE_CONNECTOR_ID, exports).then(connector=>{
nodeConnector = connector;
});

exports.getPWDRelative = async functions(subPath){
return process.cwd + "/" + subPath;
};
```

After the above, a node connector is now setup and available to use for 2 way communication.

## Executing Functions in Node from Phcode.

Suppose that you need to execute a function `getPWDRelative` in node module `y` from Phoenix.
Note that functions that are called with `execPeer` must be async and only takes a single argument.
A second optional argument can be passed which should be an ArrayBuffer to transfer large binary data(See below.).

### Calling `y.getPWDRelative` node module function from phoenix module `x.js`

```js
// in x.js
// treat it like just any other function call. The internal RPC bits are handled by the NodeConnector.
// This makes working with node APIs very eazy in phcode.
// ensure that `nodeConnector` is set before using it here as it is returned by a promise!
await nodeConnectedPromise;
const fullPath = await nodeConnector.execPeer('getPWDRelative', "sub/path.html");
```

## Executing Functions in Phcode from Node and sending binary data.

`execPeer` API accepts a single optional binary data that can be passed to the other side. In this example, we
will transfer an ArrayBuffer from node to phcode function `modifyImage` in module `x.js`

### Calling `x.modifyImage` phoenix module function from node module `y.js`

```js
// in y.js
// ensure that `nodeConnector` is set before using it here as it is returned by a promise!
await nodeConnectedPromise;
const {operationDone, buffer} = await nodeConnector.execPeer('modifyImage', "theHills.png", imageAsArrayBuffer);
```

## Events - Listening to and raising events between node and phoenix `NodeConnector`.

The nodeConnector object is an `EventDispatcher` implementing all the apis supported by `utils.EventDispatcher` API.
You can use `nodeConnector.triggerPeer(eventName, data, optionalArrayBuffer)` API to trigger an event on the other side.

### using `nodeConnector.triggerPeer(eventName, data, optionalArrayBuffer)`

Lets listen to a named `phoenixProjectOpened` event in node that will be raised from phoenix.

In `y.js` in node

```js
// in y.js
// ensure that `nodeConnector` is set before using it here as it is returned by a promise!
nodeConnector.on('phoenixProjectOpened', (_event, projectPath)={
console.log(projectPath);
});
```

Now in Phoenix module `x.js`, we can raise the event on node by using the `triggerPeer` API.

```js
// in x.js
nodeConnector.triggerPeer("phoenixProjectOpened", "/x/project/folder");
// this will now trigger the event in node.
```

To listen, unlisten and see more operations of event handling available in `nodeConnector`, see docs for
`utils/EventDispatcher` module.

### Sending binary data in events

You can optionally send binary data with `triggerPeer`. See Eg. Below.

```js
nodeConnector.triggerPeer("imageEdited", "name.png", imageArrayBuffer);
```

## createNodeConnector

Creates a new node connector with the specified ID and module exports.

Returns a promise that resolves to an NodeConnector Object (which is an EventDispatcher with
additional `execPeer` and `triggerPeer` methods. `peer` here means, if you are executing `execPeer`
in Phoenix, it will execute the named function in node side, and vice versa.
The promise will be resolved only after a call to `createNodeConnector` on the other side with the
same `nodeConnectorID` is made. This is so that once the promise is resolved, you can right away start
two-way communication (exec function, send/receive events) with the other side.

* execPeer: A function that executes a peer function with specified parameters.
* triggerPeer: A function that triggers an event to be sent to a peer.
* Also contains all the APIs supported by `utils/EventDispatcher` module.

### Parameters

* `nodeConnectorID` **[string][1]** The unique identifier for the new node connector.
* `moduleExports` **[Object][2]** The exports of the module that contains the functions to be executed on the other side.

<!---->

* Throws **[Error][3]** If a node connector with the same ID already exists.

Returns **[Promise][4]** A promise that resolves to an NodeConnector Object.

[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
13 changes: 8 additions & 5 deletions docs/generatedApiDocs/utils/EventDispatcher-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## utils/EventDispatcher

Implements a jQuery-like event dispatch pattern for non-DOM objects (works in web workers as well):
Implements a jQuery-like event dispatch pattern for non-DOM objects (works in web workers and phoenix node as well):

* Listeners are attached via on()/one() & detached via off()
* Listeners can use namespaces for easy removal
Expand Down Expand Up @@ -43,19 +43,22 @@ const EventDispatcher = brackets.getModule("utils/EventDispatcher");

### Using the global object

The EventDispatcher Object is available within the global context, be it phoenix or phoenix core web workers.
The EventDispatcher Object is available within the global context, be it phoenix or phoenix core web workers or node.

```js
window.EventDispatcher.trigger("someEvent"); // within phoenix
self.EventDispatcher.trigger("someEvent"); // within web worker
window.EventDispatcher.makeEventDispatcher(exports); // within phoenix require module
self.EventDispatcher.makeEventDispatcher(object); // within web worker
global.EventDispatcher.makeEventDispatcher(exports); // within node module that has an export
```

If you wish to import event dispatcher to your custom web worker, use the following

```js
importScripts('<relative path from your extension>/utils/EventDispatcher');
// this will add the global EventDispatcher to your web-worker. Note that the EventDispatcher in the web worker
// is a separate domain and cannot raise or listen to events in phoenix/other workers
// and node is a separate domain and cannot raise or listen to events in phoenix/other workers. For triggering events
// between different domains like between node and phcode, see `nodeConnector.triggerPeer` or
// `WorkerComm.triggerPeer` API for communication between phcode and web workers.
self.EventDispatcher.trigger("someEvent"); // within web worker
```

Expand Down
3 changes: 3 additions & 0 deletions src-node/node-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,9 @@ async function createNodeConnector(nodeConnectorID, moduleExports) {
if ((dataBuffer && !(dataBuffer instanceof ArrayBuffer)) || dataObjectToSend instanceof ArrayBuffer) {
throw new Error("execPeer should be called with exactly 3 or less arguments (FnName:string, data:Object|string, buffer:ArrayBuffer)");
}
if (dataBuffer instanceof ArrayBuffer && !_isObject(dataObjectToSend)) {
throw new Error("execPeer second argument should be an object if sending binary data (FnName:string, data:Object, buffer:ArrayBuffer)");
}
return new Promise((resolve, reject) =>{
currentCommandID ++;
pendingExecPromiseMap[currentCommandID] = {resolve, reject};
Expand Down
4 changes: 4 additions & 0 deletions src-node/test-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,8 @@ async function _shouldErrorOut(a,b) {
exports.testErrExecCases = async function () {
await _shouldErrorOut(toArrayBuffer("g"));
await _shouldErrorOut({}, 34);
let buffer = toArrayBuffer("Hello, World!");
await _shouldErrorOut("", buffer);
await _shouldErrorOut(34, buffer);
await _shouldErrorOut(null, buffer);
};
161 changes: 161 additions & 0 deletions src/NodeConnector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
* Original work Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/

// @INCLUDE_IN_API_DOCS

/**
* Use this module to communicate easily with node and vice versa. A `NodeConnector` is an intermediary between a
* module in node and a module in phcode. With a `NodeConnector` interface, you can execute a function in node
* from phoenix by simply calling `await nodeConnectorObject.execPeer("namedFunctionInNodeModule, argObject")` and
* the resolved promise will have the result. No need to do any complex IPC or anything else. See a step by step
* example below on how this is done.
*
* ## The setup
* Assume that you have a module in phcode `x.js` and another module in node `y.js`. To communicate between `x` and `y`
* we have to first create a `NodeConnector` on both sides. A `NodeConnector` will have a unique ID that will be
* same in both sides. In this example, lets set the id as `ext_x_y` where `ext` is your extension id to prevent
* name collision with other extensions. Phoenix core reserves the prefix `ph_` for internal use.
*
* ### Create `NodeConnector` in Phoenix side `x.js`
* ```js
* const NodeConnector = require('NodeConnector');
* const XY_NODE_CONNECTOR_ID = 'ext_x_y';
* let nodeConnector;
* const nodeConnectedPromise = NodeConnector.createNodeConnector(XY_NODE_CONNECTOR_ID, exports).then(connector=>{
* nodeConnector = connector;
* });
*
* exports.modifyImage = async functions(imageName, imageArrayBugger){
* // do some image ops with the imageArrayBugger
* // to return an arry buffer, you should return an object that contains a key `buffer` with the `ArrayBuffer` contents.
* return {
* operationDone: "colored,cropped",
* buffer: imageArrayBugger
* };
* };
* ```
*
* ### Create `NodeConnector` in Node `y.js`
* ```js
* const XY_NODE_CONNECTOR_ID = 'ext_x_y';
* let nodeConnector;
* const nodeConnectedPromise = global.createNodeConnector(XY_NODE_CONNECTOR_ID, exports).then(connector=>{
* nodeConnector = connector;
* });
*
* exports.getPWDRelative = async functions(subPath){
* return process.cwd + "/" + subPath;
* };
* ```
*
* After the above, a node connector is now setup and available to use for 2 way communication.
*
* ## Executing Functions in Node from Phcode.
* Suppose that you need to execute a function `getPWDRelative` in node module `y` from Phoenix.
* Note that functions that are called with `execPeer` must be async and only takes a single argument.
* A second optional argument can be passed which should be an ArrayBuffer to transfer large binary data(See below.).
*
* ### Calling `y.getPWDRelative` node module function from phoenix module `x.js`
* ```js
* // in x.js
* // treat it like just any other function call. The internal RPC bits are handled by the NodeConnector.
* // This makes working with node APIs very eazy in phcode.
* // ensure that `nodeConnector` is set before using it here as it is returned by a promise!
* await nodeConnectedPromise;
* const fullPath = await nodeConnector.execPeer('getPWDRelative', "sub/path.html");
* ```
*
* ## Executing Functions in Phcode from Node and sending binary data.
* `execPeer` API accepts a single optional binary data that can be passed to the other side. In this example, we
* will transfer an ArrayBuffer from node to phcode function `modifyImage` in module `x.js`
*
* ### Calling `x.modifyImage` phoenix module function from node module `y.js`
* ```js
* // in y.js
* // ensure that `nodeConnector` is set before using it here as it is returned by a promise!
* await nodeConnectedPromise;
* const {operationDone, buffer} = await nodeConnector.execPeer('modifyImage', "theHills.png", imageAsArrayBuffer);
* ```
*
* ## Events - Listening to and raising events between node and phoenix `NodeConnector`.
* The nodeConnector object is an `EventDispatcher` implementing all the apis supported by `utils.EventDispatcher` API.
* You can use `nodeConnector.triggerPeer(eventName, data, optionalArrayBuffer)` API to trigger an event on the other side.
*
* ### using `nodeConnector.triggerPeer(eventName, data, optionalArrayBuffer)`
* Lets listen to a named `phoenixProjectOpened` event in node that will be raised from phoenix.
*
* In `y.js` in node
* ```js
* // in y.js
* // ensure that `nodeConnector` is set before using it here as it is returned by a promise!
* nodeConnector.on('phoenixProjectOpened', (_event, projectPath)={
* console.log(projectPath);
* });
* ```
*
* Now in Phoenix module `x.js`, we can raise the event on node by using the `triggerPeer` API.
*
* ```js
* // in x.js
* nodeConnector.triggerPeer("phoenixProjectOpened", "/x/project/folder");
* // this will now trigger the event in node.
* ```
*
* To listen, unlisten and see more operations of event handling available in `nodeConnector`, see docs for
* `utils/EventDispatcher` module.
*
* ### Sending binary data in events
* You can optionally send binary data with `triggerPeer`. See Eg. Below.
* ```js
* nodeConnector.triggerPeer("imageEdited", "name.png", imageArrayBuffer);
* ```
*
* @module NodeConnector
*/

define(function (require, exports, module) {
/**
* Creates a new node connector with the specified ID and module exports.
*
* Returns a promise that resolves to an NodeConnector Object (which is an EventDispatcher with
* additional `execPeer` and `triggerPeer` methods. `peer` here means, if you are executing `execPeer`
* in Phoenix, it will execute the named function in node side, and vice versa.
* The promise will be resolved only after a call to `createNodeConnector` on the other side with the
* same `nodeConnectorID` is made. This is so that once the promise is resolved, you can right away start
* two-way communication (exec function, send/receive events) with the other side.
*
* - execPeer: A function that executes a peer function with specified parameters.
* - triggerPeer: A function that triggers an event to be sent to a peer.
* - Also contains all the APIs supported by `utils/EventDispatcher` module.
*
* @param {string} nodeConnectorID - The unique identifier for the new node connector.
* @param {Object} moduleExports - The exports of the module that contains the functions to be executed on the other side.
*
* @returns {Promise} - A promise that resolves to an NodeConnector Object.
*
* @throws {Error} - If a node connector with the same ID already exists.
*/
function createNodeConnector(nodeConnectorID, moduleExports) {
return window.PhNodeEngine.createNodeConnector(nodeConnectorID, moduleExports);
}

exports.createNodeConnector = createNodeConnector;
});
1 change: 1 addition & 0 deletions src/brackets.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ define(function (require, exports, module) {
require("utils/EventDispatcher");
require("worker/WorkerComm");
require("utils/ZipUtils");
require("NodeConnector");

// Load dependent modules
const AppInit = require("utils/AppInit"),
Expand Down
3 changes: 3 additions & 0 deletions src/node-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ function nodeLoader() {
if ((dataBuffer && !(dataBuffer instanceof ArrayBuffer)) || dataObjectToSend instanceof ArrayBuffer) {
throw new Error("execPeer should be called with exactly 3 arguments or less (FnName:string, data:Object|string, buffer:ArrayBuffer)");
}
if (dataBuffer instanceof ArrayBuffer && !_isObject(dataObjectToSend)) {
throw new Error("execPeer second argument should be an object if sending binary data (FnName:string, data:Object, buffer:ArrayBuffer)");
}
return new Promise((resolve, reject) =>{
currentCommandID ++;
pendingExecPromiseMap[currentCommandID] = {resolve, reject};
Expand Down
Loading

0 comments on commit bb77703

Please sign in to comment.