Hermes offers several services not available in a Web Browser through one (or several) Node.js processes:
- File-system abstraction for the Ares IDE.
- Archive service (used by the PhoneGap build service).
/id/{id}
resources are accessible via avery verbs described below. These resources are used to browse the folder tree & get idividual items/file/*
resources are files that are known to exist. These resources are used by the Enyo Javascript parser & by the Ares Project Preview feature. They can only be accessed usingGET
.
Hermes file-system providers use verbs that closely mimic the semantics defined by WebDAV (RFC4918): although Hermes reuses the same HTTP verbs (GET
, PUT
, PROPFIND
, MKCOL
, DELETE
...), it differs in terms of carried data. Many (if not most) of the HTTP clients implement only the GET
and POST
HTTP verbs: Hermes uses X-HTTP-Method-Overrides as WebDAV usually does. As a potential security hole (enforced by Express), Ares does not support the special _method
query parameter .
-
PROPFIND
lists properties of a resource. It recurses into the collections according to thedepth
parameter, which may be 0, 1, … etc plusinfinity
. For example, the following directory structure:$ tree dir1/ dir1/ ├── file0 └── file1
… corresponds to the following JSON object (multi-level node descriptor) returned by PROPFIND
. The node descriptor Object format is defined by this JSON schema. The path
property is node location absolute to the Hermes file-system server root: it uses URL notation: UNIX-type folder separator (/
), not Windows-like (\\
).
$ curl "http://127.0.0.1:9009/id/%2F?_method=PROPFIND&depth=10"
{
"isDir": true,
"path": "/",
"name": "",
"children": [
{
"isDir": true,
"path": "/dir1",
"name": "dir1",
"id": "12efa4560"
"children": [
{
"isDir": false,
"path": "/dir1/file0",
"name": "file0",
"id": "12efab780"
},
{
"isDir": false,
"path": "/dir1/file1",
"name": "file1",
"id": "0ae12ef56"
}
]
},
],
"id": "934789346956340",
"versionTag": "af34ef45",
}
-
MKCOL
create a collection (a folder) into the given collection, asname
passed as a query parameter (and therefore URL-encoded). It returns a JSON-encoded single-level (depth=0) node descriptor of the new folder.$ curl -d "" "http://127.0.0.1:9009/id/%2F?_method=MKCOL&name=tata"
-
GET
can be used:On files:
to get the content of a particular file.
The optional query parameterversionTag
comes from a previous call toGET
on the same file. The HTTP headerx-ares-node
(lowecase) contains a JSON-encoded version of the file's node descriptor (the one returned byPROPFIND
for this file).On folders:
to get the content of all the files of the folder encoded in base64 into a single FormData.
An additional query parameter "format" set to "base64" is expected. An error will be returned if this "format" query parameter is not present or is not equal to "base64".
Other encoding may be added later on.
-
PUT
creates or overwrite one or more file resources, provided asapplication/x-www-form-urlencoded
ormultipart/form-data
. It returns a JSON-encoded array of single-level (depth=0) node descriptors for each uploaded files.application/x-www-form-urlencoded
contains a single base64-encoded file in the form field namedcontent
. The file name and location are provided by{id}
and optionallyname
query parameter.multipart/form-data
follows the standard format. For each filefilename
is interpreted relativelly to the folder{id}
provided in the URL. Note: To accomodate an issue with old Firefox releases (eg. Firefox 10), fields labelledfilename
overwrite thefilename
in their correspondingfile
fields. SeefsBase#_putMultipart()
for more details.
-
DELETE
delete a resource (a file), which might be a collection (a folder). Status codes:-
200/OK
success, resource successfully removed. The method returns the new node descriptor (asPROPFIND
would return) of the parent of the deleted resource.$ curl -d "" "http://127.0.0.1:9009/id/%2Ftata?_method=DELETE"
-
-
COPY
reccursively copies a resource as a newname
orfolderId
provided in the query string (one of them is required, only one is taken into account,name
takes precedence if both are provided in the query-string). The optionnal query parameteroverwrite
defines whether theCOPY
should try to overwrite an existing resource or not. The method returns the node descriptor (asPROPFIND
would return) of the new resource.201/Created
success, a new resource is created200/Ok
success, an existing resource was successfully overwritten (query parameteroverwrite
was set totrue
)412/Precondition-Failed
failure, not allowed to copy onto an exising resource
-
MOVE
has the exact same parameters and return code & value asCOPY
http://127.0.0.1:{port}/{urlPrefix}/id/{id}
port
TCP porturlPrefix
server pathid
resource IDs. Even when readable, those resources need to be handled as if they were opaque values.
name
File or folder name, for creation methods (MKCOL
,PUT
,COPY
,MOVE
). This is required, as the resourceid
is not (yet) known.folderId
target folderid
used byCOPY
andMOVE
methods.overwrite
when set totrue
with theCOPY
,MOVE
andPUT
methods, causes the target resource to be overwritten (not merged) in case it already exists. When absent,overwrite
defaults tofalse
.depth
is only valid with thePROPFIND
method. It defines the recursion level of the request in the folder tree. When omitted, it defaults to1
(list immediate child resources of a folder). The special valueinfinity
causes the methods to recurse to the deepest possible level in the folder tree.
New the test suite does not cover the former verbs used by Hermes, and implemented into filesystem/index.js
.
So far, only hermes local filesystem access comes with a (small) test suite, that relies on Mocha and Should.js. Run it using:
$ test/mocha/bin/mocha hermes/fsLocal.spec.js
To stop on the first failing case:
$ test/mocha/bin/mocha --bail hermes/fsLocal.spec.js
For more detailled instructions, refer to the Mocha home page.
Ares comes with an Hermes service using your Dropbox account as a storage service. Enable this service in the ide.json
before starting the IDE server:
[…]
{
"active":true,
"id":"dropbox",
"icon":"dropbox.com-32x32",
"name":"Dropbox",
"type": "filesystem",
"provider": "hermes",
"command":"@NODE@", "params":[
"hermes/fsDropbox.js", "-P", "/files", "-p", "10002"
],
"auth": {
"type": "dropbox",
"appKey": "",
"appSecret": ""
},
"useJsonp":false,
"verbose": false
[…]
You need to replace the appKey and appSecret entries with the proper values from your Dropbox application entry for Ares(see below).
In order to use Dropbox as storage service for Ares, you need to create an Ares application in Dropbox & grant Ares the authorization to access this Dropbox application (Ares > Accounts > Dropbox > Renew ). Popup blockers must be disabled to allow the Dropbox OAuth popup window to appear.
NOTE: While Chrome & Firefox will notify you of a blocked popup (hence allowing you to explicitly un-block it), Safari users will need to explicitly allow every popups (unless there is s smarter way am not aware of) using Safari > Preferences > Security > Un-check Block pop-up windows
NOTE: Ares gives 20 seconds to the browser to load the Dropbox authorization window & complete the procedure. In case it takes longer, please press Renew again: another immediate attempt will be faster as the page will be partially available from the browser cache.
Ares Dropbox connector works behind an enterprise HTTP/HTTPS proxy, thanks to the GitHub:node-tunnel library. fsDropbox
proxy configuration embeds a node-tunnel
configuration. For example, fellow-HP-ers can use the below (transform Xproxy
into proxy
in the sample ide.json):
[…]
"proxy":{
"http":{
"tunnel":"OverHttp",
"host":"web-proxy.corp.hp.com",
"port":8080
},
"https":{
"tunnel":"OverHttp",
"host":"web-proxy.corp.hp.com",
"port":8080
}
},
[…]
This is the arZip.js
service. It takes 2 arguments:
pathname
port
It can be started standlone using the following command-line (or a similar one):
…in which case it can be tested using curl
by a command-line like the following one:
$ curl \
-F "file=@config.xml;filename=config.xml" \
-F "file=@icon.png;filename=images/icon.png" \
-F "archive=myapp.zip" \
"http://127.0.0.1:9019/arZip" > /tmp/toto.zip
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8387 0 3860 100 4527 216k 253k --:--:-- --:--:-- --:--:-- 276k
The generated file is expected to look like to below:
$ unzip -l /tmp/toto.zip
Archive: /tmp/toto.zip
Length Date Time Name
-------- ---- ---- ----
942 10-17-12 17:26 config.xml
3126 10-17-12 17:26 images/icon.png
-------- -------
4068 2 files
The service "genZip" allows to intanciate new Ares project from project templates such as "bootplate" or any customer specific project templates. These project templates can be defined:
- in "ide.json" of ares-project
- or in "ide.json" of Ares plugins
IMPORTANT:
See Project template configuration and Merging Ares plugin configuration for more information.
The property projectTemplateRepositories
of the service "genZip" lists the template definitions that are available at project creation time.
The property projectTemplateRepositories
of the service "genZip" is defined in the "ide.json" of ares-project.
{
"id":"genZip",
"projectTemplateRepositories": {
"bootplate": {
"description": "Standard Enyo template",
"url": "http://enyojs.com/archive/ares-project-templates.json"
}
},
...
}
The url
property above can either be an http url or a an absolute filename such as:
NB: Within the ide.json
configuration file, the variables @NODE@
, @CWD@
, @INSTALLDIR@
, @HOME@
and @PLUGINDIR@
will be subsituted by the right value when node ide.js
is started.
"url": "@INSTALLDIR@/templates/projects/ares-project-templates.json"
Ares plugins can add or modify this list of templates.
{
"id": "genZip",
"projectTemplateRepositories": {
"bootplate": {
},
"my-templates": {
"url": "http://xyz.com/my-templates.json",
"description": "My Templates"
}
}
}
As a result, "bootplate": {}
will remove the entry defined in ide.js of ares-project and "my-templates": { "url" : "http://xyz.com/my-templates.json", …}
will add a new list of templates named 'my-templates'.
A project template definition (defined by the property url
in projectTemplateRepositories
must respect the json schema com.enyojs.ares.project.templates.schema.json.
The compliance of a project template definition file with the json schema is not yet enforced but could be checked via http://jsonschemalint.com/.
[
{
"id": "bootplate-2.2.0",
"zipfiles": [
{
"url": "bootplate-2.2.0.zip",
"alternateUrl": "http://enyojs.com/archive/bootplate-2.2.0.zip",
"prefixToRemove": "bootplate",
"excluded": [
"bootplate/api"
]
},
{
"url": ...
}
],
"description": "Enyo bootplate 2.2.0"
},
{
"id": ...
}
]
Each project definition defined by id
can reference several zip files defined in the array zipfiles
. The zip files are extracted in the order they are specified.
Each zip file entry:
-
must define an
url
and optionally analternateUrl
. Theurl
is tried first and can refer to either:-
a file stored locally on the filesystem.
NB: the filename must be relative to the directory where the project templates definition file is stored (See propertyurl
inprojectTemplateRepositories
above). -
or an http url.
If the
url
references a file which does not exist thealternateUrl
is used. -
-
can define a
prefixToRemove
. This prefix must correspond to one or several directory level that must be removed. -
can define in the array
excluded
a list of files or directories to be excluded when the zip file is extracted.
The default <pathname>
value is /genzip
. Its value can be changed using the -P
paraemter in the main ide.json
configuration file.
<pathname>/templates
:GET
returns a JSON-encoded array of all the available project templates.
<pathname>/template-repos/:repo-id
:POST
creates a new repo entry with the keyrepo-id
. The POST body must contain theurl
of the new project template repository.
<pathname>/generate
:POST
returns a FormData containing all the files, encoded in base64, of the selected project template.- The POST body must contain:
- the
templateId
of the selected project template. - the
substitutions
needed as a JSON stringified object.
- the
- It's up to the caller to extract and base64 decode the files to create a new project.
In Ares, this is achieved by forwarding the FormData to Hermes filesystem service via a PUT method.
- The POST body must contain:
MyBuildService#buildPkg(project)
whereproject
is an instance ofAres.Model.Project
as found in theProjectView
. This kind has (among others) the following entry points:Ares.Model.Project#getName()
: Application NameAres.Model.Project#getService()
: File-System service hosting the application project (source code)Ares.Model.Project#getFolderId()
: File-System service folderId of the application project (source code) root folderAres.Model.Project#getConfig
: Application project configuration (fromproject.json
) for that specific builder (frombuild.<builderName>
, as set by the service UI)
Although it can rely on other remote or local resources, Ares build services may share a maximum amount of source code with other build services by implementing the following resources using a shared semantic.
The following resources
<pathname>/config
:- POST: pass the
ide.json
consolidated content for this specific service. This resource is only accessed from the Ares server: it is not proxied to the Ares IDE.- Query-Params: n/a
- Request-Headers: n/a
- Request-Body:
application/json
the consolidatedide.json
for this specific service
- Response-Headers: n/a
- Response-Body: n/a
- POST: pass the
<pathname>/user
:- GET: return user account information. Might be used to set a client-side cookie with proper authentication. This resource is generally called from the Ares Accounts configuration wizard (the Ares IDE plugin that comes with a build service).
<pathname>/op/build
- POST: start a new build
- Query-Params: n/a
- Request-Headers:
Content-Type:
(Authorization:
orCookies:
headers may also be present) - Request-Body:
multipart/form-data
sequence of files that constitute the application source code. The relative file location is encoded the URL-way (with/
directory separator) in each partfilename
field. The per-partcontent-type
is always ignored & considered asapplication/octet-stream
in order to not modifiy the original application source code before passing to the back-end build service.
- Response-Headers: n/a
- Response-Body:
text/plain
build has failed (see HTTP response code) or is asynchronousmultipart/form-data
generated application package is the first returned part of the multipart response
- POST: start a new build
The entire build.phonegap.com API is wrapped by a dedicated Hermes build service named bdPhoneGap
. Reasons are:
-
It is easier (more portable) to manipulate files using Node.js than using HTML5
File
andBlob
entities, which are not (yet) fully implemented by the browsers. -
build.phonegap.com does not support CORS: It refuses to answer an AJAX query (or at least the one that requests a token) that comes from a web application served from 127.0.0.1 (as Ares is in its standalone version).
XMLHttpRequest cannot load https://build.phonegap.com/token. Origin http://127.0.0.1 is not allowed by Access-Control-Allow-Origin.
Note: Ares PhoneGap Build connector does not work behind an HTTP/HTTPS proxy yet.
Resources:
- The default
<pathname>
value is/phonegap
. Its value can be changed using the-P
paraemter in the mainide.json
configuration file. <pathname>/token
:GET
using Basic Authentication, returns a JSON-encoded token object{"token":"YOUR_TOKEN"}
. Every other resources are accessible only when passing this token value using on of the following ways:- As a
token
cookie with valueYOUR_TOKEN
(preferred), - As a web-form
token
field (either inapplication/x-www-urlencoded
ormultipart/forma-data
format) if applicable to the request format. - As a
token=YOUR_TOKEN
query parameter, in the URL (least secure, may be removed in the future).
<pathname>/user
:GET
, returns the user account information, using the same format as the one returned byGET https://build.phonegap.com/api/v1/me
.
<pathname>/deploy
:POST
runs the Enyodeploy.js
script on the given application. Application folder tree is encoded as amultipart/form-data
in the POST request body. If successfull, the response body is aapplication/zip
containing the deployable application. It is an intermediate step of the<pathname>/build
operation.
<pathname>/build
:POST
uploads the former deployable application to the PhoneGap Build Service & returns the JSON-encoded answer of this service. This operation expects some fields (key=value
) to be passed inlined in the request body (asmultipart/form-data
fields). Each of those field is passed verbatim as a property of the JSON requestObject (see mantatory & optionnal parameters of the PhoneGap write API). The following fields are mandatory.title
is the human-readable application name. It is considered a good practice to reuse the value of the<name/>
field of theconfig.xml
.
Note: Most of the commands in this section assume that you are using Mac OSX or Linux.
Manual run on fixed port 10003 (default is dynamically-assigned port):
$ node hermes/bdPhoneGap.js -p 10003
Test /token
:
-
Assuming you have a working account build.phonegap.com associated with the email
YOUR_EMAIL
, get your PhoneGap developer tokenYOUR_TOKEN
, using build.phonegap.com:$ curl -v -u YOUR_EMAIL -X POST -d "" https://build.phonegap.com/token Enter host password for user 'YOUR_EMAIL': {"token":"YOUR_TOKEN"}
-
Using bdPhoneGap:
$ curl -v -X POST -d "username=YOUR_EMAIL" -d "password=YOUR_PASSWORD" http://127.0.0.1:10003/phonegap/token {"token":"YOUR_TOKEN"}
Test /api/v1/me
:
-
Using build.phonegap.com, use one of the below:
$ curl -v -u YOUR_EMAIL -X GET -d "" https://build.phonegap.com/api/v1/me $ curl -v -X GET https://build.phonegap.com/api/v1/me?auth_token=YOUR_TOKEN
-
Using bdPhoneGap:
$ curl -v -X GET -b "token=YOUR_TOKEN" http://127.0.0.1:10003/phonegap/api/v1/me
Test /op/deploy
:
-
The following commands generates a POST request carrying a
multipart/form-data
message suitable to test thebdPhoneGap.js
service: -
This one generates a command that returns a minified (deployable) application
$ find . -type f | \ awk 'BEGIN{printf("curl ");}{sub("^\.\/", "", $1); printf("-F \"file=@%s;filename=%s\" ", $1, $1);}END{print(url)}' \ url=http://127.0.0.1:9029/phonegap/deploy/
Test /op/build
:
-
This one generates a command that creates a new
appId
using an application located in the current directory. Note that you must provide thetoken
andtitle
form fields. The<name/>
in theconfig.xml
is a suitable value for thetitle
form field.$ find . -type f | \ awk 'BEGIN{printf("curl ");}{sub("^\.\/", "", $1); printf("-F \"file=@%s;filename=%s\" ", $1, $1);}END{printf("-F \"token=%s\" -F \"title=%s\" %s\n", token, title, url)}' \ token=YOUR_TOKEN \ title="My First PhoneGap App" \ url=http://127.0.0.1:9029/phonegap/build/
-
This one generates a command that updates the
appId=238006
. Other form fields follow the same rules as before.$ find . -type f | \ awk 'BEGIN{printf("curl ");}{sub("^\.\/", "", $1); printf("-F \"file=@%s;filename=%s\" ", $1, $1);}END{printf("-F \"token=%s\" -F \"title=%s\" -F \"appId=%s\" %s\n", token, title, appId, url)}' \ token=YOUR_TOKEN \ title="My First PhoneGap App" \ appId=YOUR_APPID \ url=http://127.0.0.1:9029/phonegap/build/
Note: In case you are working from behind the HP firewall (without ), you may need to prefix the curl
and node
commands above with env https_proxy=web-proxy.corp.hp.com:8080
.
TBC
MyTestService#runApp(project)
MyTestService#debugApp(project)
TBC
It is possible to debug an Hermes services by commenting-out the command
property of the service to be debugged in the ide.json & start it manually along with --debug
or --debug-brk
to later connect a node-inspector
.
Start the file server:
$ node ares-project/hermes/filesystem/index.js 9010 hermes/filesystem/root
Debugging: The following sequence (to be run in separated terminals) opens the ARES local file server in debug-mode using node-inspector
.
$ node --debug ares-project/hermes/filesystem/index.js 9010 hermes/filesystem/root
...then start node-inspector
& the browser windows from a separated terminal:
$ open -a Chromium http://localhost:9010/ide/ares/index.html
$ node-inspector &
$ open -a Chromium http://localhost:8080/debug?port=5858
References:
- GitHub: node-inspector
- Using node-inspector to debug node.js applications including on Windows (and using ryppi for modules)
- Screencast Debugging with
node-inspector
- RFC4918 - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)
- RFC5689 - Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)