From 6c47f50637b9355bb16436aa19d1fe13df05d783 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Mon, 6 May 2024 22:45:22 -0400 Subject: [PATCH 01/23] Update environment proxy variable detection to use proxy-from-env library, ensure node-fetch and axios use common method to apply custom http and https agents, version bump --- package.json | 5 +++-- src/engine.js | 27 ++++++++++++++++++++++++++- src/engines/axios/engine.js | 22 ++++++++++------------ src/engines/fetch/engine.js | 11 +++++++++++ 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 80208b3..c7c2d10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.23", + "version": "0.2.24", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" @@ -38,7 +38,8 @@ "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.4", "moment": "^2.30.1", - "node-fetch": "^2.7.0" + "node-fetch": "^2.7.0", + "proxy-from-env": "^1.1.0" }, "scripts": { "alltests": "mocha 'test/**/*.ts' --recursive --timeout 120000 --watch-extensions ts,js", diff --git a/src/engine.js b/src/engine.js index 7cb2636..abc2efa 100644 --- a/src/engine.js +++ b/src/engine.js @@ -1,6 +1,9 @@ var Helper = require("./helper"); const moment = require("moment/moment"); -const {refreshToken, ownerCredentials} = require("axios-oauth-client"); + +const { HttpsProxyAgent } = require("https-proxy-agent"); +const { HttpProxyAgent } = require("http-proxy-agent"); +const { getProxyForUrl } = require("proxy-from-env"); var ONE_HOUR_MS = 1000 * 60 * 60; @@ -201,6 +204,28 @@ class Engine }); }; + // checks HTTP_PROXY, HTTPS_PROXY and NO_PROXY environment variables + // determines whether a proxy should be configured (otherwise null) + // sets proxy settings onto options + this.applyAgent = function(url, fn) + { + // checks HTTP_PROXY, HTTPS_PROXY and NO_PROXY environment variables + // determines whether a proxy should be configured (otherwise null) + var proxy = getProxyForUrl(url); + if (proxy) + { + // bind in http proxy + if (url.startsWith("https:")) + { + fn(null, new HttpsProxyAgent(proxy)); + } + else if (config.baseURL.startsWith("http:")) + { + fn(new HttpProxyAgent(proxy)); + } + } + }; + // @abstract this.buildGetHandler = function(uri, qs) { diff --git a/src/engines/axios/engine.js b/src/engines/axios/engine.js index 8a290e4..bd17668 100644 --- a/src/engines/axios/engine.js +++ b/src/engines/axios/engine.js @@ -3,9 +3,6 @@ var Helper = require("../../helper"); var axios = require('axios'); -const { HttpsProxyAgent} = require("https-proxy-agent"); -const { HttpProxyAgent} = require("http-proxy-agent"); - var { ownerCredentials, refreshToken } = require("axios-oauth-client"); class AxiosEngine extends Engine @@ -30,20 +27,20 @@ class AxiosEngine extends Engine } } - if (process.env.HTTP_PROXY) { - axiosClientConfig.proxy = false; - axiosClientConfig.httpAgent = new HttpProxyAgent(process.env.HTTP_PROXY); - } + self.applyAgent(config.baseURL, function(httpProxyAgent, httpsProxyAgent) { - if (process.env.HTTPS_PROXY) { - axiosClientConfig.proxy = false; - axiosClientConfig.httpsAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); - } + if (httpProxyAgent) { + axiosClientConfig.proxy = false; + axiosClientConfig.httpAgent = httpProxyAgent; + } else if (httpsProxyAgent) { + axiosClientConfig.proxy = false; + axiosClientConfig.httpsAgent = httpsProxyAgent; + } + }); // build axios client var client = self.client = axios.create(axiosClientConfig); - //// this.doRequest = function(options, callback) @@ -55,6 +52,7 @@ class AxiosEngine extends Engine var err = null; var signedOptions = self.incoming(options); + return client.request(signedOptions) .then(function(_response) { response = _response; diff --git a/src/engines/fetch/engine.js b/src/engines/fetch/engine.js index b504435..b4f80b1 100644 --- a/src/engines/fetch/engine.js +++ b/src/engines/fetch/engine.js @@ -8,6 +8,8 @@ class FetchEngine extends Engine { super(config, credentials, storage, options); + var self = this; + this.doRequest = (options, callback) => { var stats = {}; @@ -22,6 +24,15 @@ class FetchEngine extends Engine var signedOptions = this.incoming(options); + self.applyAgent(signedOptions.url, function(httpProxyAgent, httpsProxyAgent) { + + if (httpProxyAgent) { + signedOptions.agent = httpProxyAgent; + } else if (httpsProxyAgent) { + signedOptions.agent = httpsProxyAgent; + } + }); + // fetch fetch(options.url, signedOptions) .then(async function(_response) { From 523f15754d41125473ef030e26a98a5ee16b3cf7 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Mon, 6 May 2024 23:03:32 -0400 Subject: [PATCH 02/23] Updated readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index e87f7ce..a4b1669 100644 --- a/README.md +++ b/README.md @@ -334,6 +334,31 @@ process.env.HTTPS_PROXY = "http://localhost:9090"; })(); ``` +In addition, the following environment variables are supported to prevent certain domains from routing +through the HTTP/HTTPS PROXY endpoints. + +- `NO_PROXY` +- `*_PROXY` + +For more information, see: +https://github.com/Rob--W/proxy-from-env?tab=readme-ov-file#environment-variables + +Example: + +```javascript +const cloudcms = require("cloudcms"); + +process.env.HTTPS_PROXY = "http://localhost:9090"; + +// route everything through "localhost:9090" except connections to https://api.cloudcms.com +process.env.NO_PROXY = "api.cloudcms.com"; + +(async function() { + var session = await cloudcms.connect(); + console.log("Connected!"); +})(); +``` + ## Custom Engine Use the `engine()` method to select the underlying HTTP client engine to use for connectivity to the API. From 3dab0a99eefb095aff629ef84bd4d8bcee7480b3 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Mon, 17 Jun 2024 10:22:06 -0400 Subject: [PATCH 03/23] Fix to applyAgent to support "http" detection properly --- src/engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine.js b/src/engine.js index 4316c82..1e7e869 100644 --- a/src/engine.js +++ b/src/engine.js @@ -219,7 +219,7 @@ class Engine { fn(null, new HttpsProxyAgent(proxy)); } - else if (config.baseURL.startsWith("http:")) + else if (url.startsWith("http:")) { fn(new HttpProxyAgent(proxy)); } From b4a11509ba3d8257156da1b3eb8b7495d11e355f Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 5 Jul 2024 00:42:30 -0400 Subject: [PATCH 04/23] Remove reliance on third party `axios-oauth-client` dependency as it did not support supplying client key/secret via headers, switch to using direct HTTP/S calls for owner and refresh token flows --- package.json | 3 +- src/engines/axios/engine.js | 85 +++++++++++++++++++++++++++++++++++-- src/engines/fetch/engine.js | 2 - 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index c7c2d10..b88b01d 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,7 @@ "typescript": "^5.4.5" }, "dependencies": { - "axios": "^1.6.8", - "axios-oauth-client": "^2.2.0", + "axios": "^1.7.2", "cheerio": "^1.0.0-rc.10", "form-data": "^4.0.0", "http-proxy-agent": "^7.0.2", diff --git a/src/engines/axios/engine.js b/src/engines/axios/engine.js index bd17668..abd2b15 100644 --- a/src/engines/axios/engine.js +++ b/src/engines/axios/engine.js @@ -3,8 +3,6 @@ var Helper = require("../../helper"); var axios = require('axios'); -var { ownerCredentials, refreshToken } = require("axios-oauth-client"); - class AxiosEngine extends Engine { constructor(config, credentials, storage, options) @@ -394,12 +392,91 @@ class AxiosEngine extends Engine this.buildGetRefreshToken = function() { - return refreshToken(client, [config.baseURL, "oauth/token"].join("/"), config.clientKey, config.clientSecret); + var self = this; + + return function(refreshToken, optionalScopes) + { + return new Promise(function(resolve, reject) { + + var url = [config.baseURL, "oauth/token"].join("/"); + var clientKey = config.clientKey; + var clientSecret = config.clientSecret; + + if (!optionalScopes || optionalScopes.length === 0) { + optionalScopes = "api"; + } + var scopes = optionalScopes.join(","); + + var params = { + "grant_type": "refresh_token", + "refresh_token": refreshToken, + "scope": scopes + }; + + var headers = { + "Authorization": "Basic " + Buffer.from(clientKey + ":" + clientSecret, "utf-8").toString("base64") + }; + + self.doRequest({ + "url": url, + "method": "POST", + "params": params, + "headers": headers + }, function(err, response, data, stats) { + + if (err) { + return reject(err); + } + + resolve(data); + }); + }); + }; }; this.buildGetOwnerCredentials = function() { - return ownerCredentials(client, [config.baseURL, "oauth/token"].join("/"), config.clientKey, config.clientSecret); + var self = this; + + return function(username, password, optionalScopes) + { + return new Promise(function(resolve, reject) { + + var url = [config.baseURL, "oauth/token"].join("/"); + var clientKey = config.clientKey; + var clientSecret = config.clientSecret; + + if (!optionalScopes || optionalScopes.length === 0) { + optionalScopes = "api"; + } + var scopes = optionalScopes.join(","); + + var params = { + "grant_type": "password", + "username": username, + "password": password, + "scope": scopes + }; + + var headers = { + "Authorization": "Basic " + Buffer.from(clientKey + ":" + clientSecret, "utf-8").toString("base64") + }; + + self.doRequest({ + "url": url, + "method": "POST", + "params": params, + "headers": headers + }, function(err, response, data, stats) { + + if (err) { + return reject(err); + } + + resolve(data); + }); + }); + }; }; } } diff --git a/src/engines/fetch/engine.js b/src/engines/fetch/engine.js index a51b2ad..749178a 100644 --- a/src/engines/fetch/engine.js +++ b/src/engines/fetch/engine.js @@ -428,7 +428,6 @@ class FetchEngine extends Engine } }; - // Not Implemented this.buildGetRefreshToken = function() { var self = this; @@ -473,7 +472,6 @@ class FetchEngine extends Engine }; }; - // Not Implemented this.buildGetOwnerCredentials = function() { var self = this; From fd63a7b843c80ce54def39cb74876858dc74913f Mon Sep 17 00:00:00 2001 From: uzquiano Date: Thu, 1 Aug 2024 01:36:22 -0400 Subject: [PATCH 05/23] Added session.useBand() method to allow sessions to be bound to specific bands, refactored engines to allow headers to be specified on get/put/post etc. --- README.md | 16 ++++++++ src/engine.js | 61 ++++++++++++++++++----------- src/engines/axios/engine.js | 70 ++++++++++++++++++++++++++++++---- src/engines/fetch/engine.js | 68 +++++++++++++++++++++++++++++---- src/session/default/session.js | 60 +++++++++++++++++++++++++---- 5 files changed, 231 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index a4b1669..e0b2ced 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,22 @@ You can also manually expire the issued Access and Refresh Token, like this: await session.disconnect(); ``` +### Band + +If you have multiple bands configured, you can configure your Session to perform all of its API calls +against a designated band, like this: + +```javascript +session = await cloudcms.connect(); +session.useBand("production"); +``` + +To revert back to the default band: + +```javascript +session.useBand(null); +``` + ### TypeScript The `cloudcms-javascript-driver` includes a TypeScript type interface to improve your editing experience and allow better integration in your TypeScript apps. diff --git a/src/engine.js b/src/engine.js index 1e7e869..90d1c39 100644 --- a/src/engine.js +++ b/src/engine.js @@ -227,7 +227,7 @@ class Engine }; // @abstract - this.buildGetHandler = function(uri, qs) + this.buildGetHandler = function(uri, qs, headers) { return function(done) { @@ -236,7 +236,7 @@ class Engine }; // @abstract - this.buildPostHandler = function(uri, qs, payload) + this.buildPostHandler = function(uri, qs, payload, headers) { return function(done) { @@ -245,7 +245,7 @@ class Engine }; // @abstract - this.buildPutHandler = function(uri, qs, payload) + this.buildPutHandler = function(uri, qs, payload, headers) { return function(done) { @@ -254,7 +254,7 @@ class Engine }; // @abstract - this.buildDeleteHandler = function(uri, qs) + this.buildDeleteHandler = function(uri, qs, headers) { return function(done) { @@ -263,7 +263,7 @@ class Engine }; // @abstract - this.buildPatchHandler = function(uri, qs, payload) + this.buildPatchHandler = function(uri, qs, payload, headers) { return function(done) { @@ -272,7 +272,16 @@ class Engine }; // @abstract - this.buildMultipartPostHandler = function(uri, qs, payload) + this.buildMultipartPostHandler = function(uri, qs, payload, headers) + { + return function(done) + { + // TODO + } + } + + // @abstract + this.buildDownloadHandler = function(uri, params, headers) { return function(done) { @@ -306,10 +315,11 @@ class Engine * * @param uri * @param qs + * @param headers */ - get(uri, qs, callback) + get(uri, qs, headers, callback) { - var fn = this.buildGetHandler(uri, qs); + var fn = this.buildGetHandler(uri, qs, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -340,10 +350,11 @@ class Engine * @param uri * @param qs * @param payload + * @param headers */ - post(uri, qs, payload, callback) + post(uri, qs, payload, headers, callback) { - var fn = this.buildPostHandler(uri, qs, payload); + var fn = this.buildPostHandler(uri, qs, payload, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -374,10 +385,11 @@ class Engine * @param uri * @param qs * @param payload + * @param headers */ - put(uri, qs, payload, callback) + put(uri, qs, payload, headers, callback) { - var fn = this.buildPutHandler(uri, qs, payload); + var fn = this.buildPutHandler(uri, qs, payload, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -407,10 +419,11 @@ class Engine * * @param uri * @param qs + * @param headers */ - del(uri, qs, callback) + del(uri, qs, headers, callback) { - var fn = this.buildDelHandler(uri, qs); + var fn = this.buildDeleteHandler(uri, qs, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -441,10 +454,11 @@ class Engine * @param uri * @param qs * @param payload + * @param headers */ - patch(uri, qs, payload, callback) + patch(uri, qs, payload, headers, callback) { - var fn = this.buildPatchHandler(uri, qs, payload); + var fn = this.buildPatchHandler(uri, qs, payload, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -474,11 +488,13 @@ class Engine * requestObject * * @param uri - * @param parts + * @param qs + * @param payload + * @param headers */ - multipartPost(uri, qs, formData, callback) + multipartPost(uri, qs, payload, headers, callback) { - var fn = this.buildMultipartPostHandler(uri, qs, formData); + var fn = this.buildMultipartPostHandler(uri, qs, payload, headers); // support for callback approach if (callback && Helper.isFunction(callback)) @@ -506,14 +522,13 @@ class Engine /** * @extension_point * - * - * * @param uri * @param qs + * @param headesr */ - download(uri, qs, callback) + download(uri, qs, headers, callback) { - var fn = this.buildDownloadHandler(uri, qs); + var fn = this.buildDownloadHandler(uri, qs, headers); // support for callback approach if (callback && Helper.isFunction(callback)) diff --git a/src/engines/axios/engine.js b/src/engines/axios/engine.js index abd2b15..c303333 100644 --- a/src/engines/axios/engine.js +++ b/src/engines/axios/engine.js @@ -96,7 +96,7 @@ class AxiosEngine extends Engine } // @abstract - this.buildGetHandler = function(uri, params) + this.buildGetHandler = function(uri, params, headers) { var self = this; @@ -117,6 +117,14 @@ class AxiosEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (self.isNotFound(err, response)) { @@ -138,7 +146,7 @@ class AxiosEngine extends Engine }; // @abstract - this.buildPostHandler = function(uri, params, payload) + this.buildPostHandler = function(uri, params, payload, headers) { var self = this; @@ -173,6 +181,14 @@ class AxiosEngine extends Engine options.data = payload; } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -185,7 +201,7 @@ class AxiosEngine extends Engine }; // @abstract - this.buildPutHandler = function(uri, params, payload) + this.buildPutHandler = function(uri, params, payload, headers) { var self = this; @@ -212,6 +228,14 @@ class AxiosEngine extends Engine options.data = JSON.stringify(payload); } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -224,7 +248,7 @@ class AxiosEngine extends Engine }; // @abstract - this.buildDelHandler = function(uri, params) + this.buildDeleteHandler = function(uri, params, headers) { var self = this; @@ -245,6 +269,14 @@ class AxiosEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -258,7 +290,7 @@ class AxiosEngine extends Engine }; // @abstract - this.buildPatchHandler = function(uri, params, payload) + this.buildPatchHandler = function(uri, params, payload, headers) { var self = this; @@ -285,6 +317,14 @@ class AxiosEngine extends Engine options.data = JSON.stringify(data); } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -296,7 +336,7 @@ class AxiosEngine extends Engine } }; - this.buildMultipartPostHandler = function(uri, params, payload) + this.buildMultipartPostHandler = function(uri, params, payload, headers) { var self = this; @@ -334,6 +374,14 @@ class AxiosEngine extends Engine options.data = payload; } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + options.maxBodyLength = 1000000000; self.request(options, function(err, response, data, stats) { @@ -348,7 +396,7 @@ class AxiosEngine extends Engine }, // @abstract - this.buildDownloadHandler = function(uri, params) + this.buildDownloadHandler = function(uri, params, headers) { var self = this; @@ -370,6 +418,14 @@ class AxiosEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (self.isNotFound(err, response)) { diff --git a/src/engines/fetch/engine.js b/src/engines/fetch/engine.js index 749178a..ddc358b 100644 --- a/src/engines/fetch/engine.js +++ b/src/engines/fetch/engine.js @@ -110,7 +110,7 @@ class FetchEngine extends Engine }; // @abstract - this.buildGetHandler = function(uri, params) + this.buildGetHandler = function(uri, params, headers) { var self = this; @@ -131,6 +131,14 @@ class FetchEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (self.isNotFound(err, response)) @@ -153,7 +161,7 @@ class FetchEngine extends Engine }; // @abstract - this.buildPostHandler = function(uri, params, payload) + this.buildPostHandler = function(uri, params, payload, headers) { var self = this; @@ -188,6 +196,13 @@ class FetchEngine extends Engine // options.body = payload; } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } self.request(options, function(err, response, data, stats) { if (err) { @@ -232,6 +247,14 @@ class FetchEngine extends Engine options.body = JSON.stringify(payload); } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -249,7 +272,7 @@ class FetchEngine extends Engine }; // @abstract - this.buildDelHandler = function(uri, params) + this.buildDeleteHandler = function(uri, params, headers) { var self = this; @@ -270,6 +293,14 @@ class FetchEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -287,7 +318,7 @@ class FetchEngine extends Engine }; // @abstract - this.buildPatchHandler = function(uri, params, payload) + this.buildPatchHandler = function(uri, params, payload, headers) { var self = this; @@ -314,6 +345,14 @@ class FetchEngine extends Engine options.body = JSON.stringify(data); } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -330,9 +369,8 @@ class FetchEngine extends Engine } }; - // This is the bit that still isn't working! - this.buildMultipartPostHandler = function(uri, params, payload) + this.buildMultipartPostHandler = function(uri, params, payload, headers) { var self = this; @@ -369,6 +407,14 @@ class FetchEngine extends Engine options.body = payload; } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (err) { @@ -386,7 +432,7 @@ class FetchEngine extends Engine }, // @abstract - this.buildDownloadHandler = function(uri, params) + this.buildDownloadHandler = function(uri, params, headers) { var self = this; @@ -408,6 +454,14 @@ class FetchEngine extends Engine } } + if (headers) + { + for (var k in headers) + { + options.headers[k] = headers[k]; + } + } + self.request(options, function(err, response, data, stats) { if (self.isNotFound(err, response)) { diff --git a/src/session/default/session.js b/src/session/default/session.js index 17a52a3..67c6747 100644 --- a/src/session/default/session.js +++ b/src/session/default/session.js @@ -15,6 +15,9 @@ class DefaultSession } }; + // band is assumed null (default) + this.band = null; + // helper method this.acquireId = function(objOrId) { @@ -83,9 +86,28 @@ class DefaultSession qs[k] = this.defaults.qs[k]; } + // pass band via query string? + // if (this.band) + // { + // qs["band"] = this.band; + // } + return qs; }; + this.generateHeaders = function() + { + var headers = {}; + + // pass band via headers? + if (this.band) + { + headers["x-cloudcms-band-id"] = this.band; + } + + return headers; + }; + this.extractOptionalCallback = function(_arguments) { var args = Array.prototype.slice.call(_arguments); @@ -108,7 +130,9 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.get(uri, qs, callback); + var headers = this.generateHeaders(); + + return this.engine.get(uri, qs, headers, callback); }; this.post = function(uri, qs, payload, callback) @@ -117,7 +141,9 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.post(uri, qs, payload, callback); + var headers = this.generateHeaders(); + + return this.engine.post(uri, qs, payload, headers, callback); }; this.put = function(uri, qs, payload, callback) @@ -126,7 +152,9 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.put(uri, qs, payload, callback); + var headers = this.generateHeaders(); + + return this.engine.put(uri, qs, payload, headers, callback); }; this.del = function(uri, qs, callback) @@ -135,7 +163,9 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.del(uri, qs, callback); + var headers = this.generateHeaders(); + + return this.engine.del(uri, qs, headers, callback); }; this.patch = function(uri, qs, payload, callback) @@ -144,14 +174,18 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.patch(uri, qs, payload, callback); + var headers = this.generateHeaders(); + + return this.engine.patch(uri, qs, payload, headers, callback); }; this.multipartPost = function(uri, qs, formData, callback) { var self = this; - return this.engine.multipartPost(uri, qs, formData, callback); + var headers = this.generateHeaders(); + + return this.engine.multipartPost(uri, qs, formData, headers, callback); }; this.download = function(uri, qs, callback) @@ -160,10 +194,22 @@ class DefaultSession qs = this.populateDefaultQs(qs); - return this.engine.download(uri, qs, callback); + var headers = this.generateHeaders(); + + return this.engine.download(uri, qs, headers, callback); }; } + /** + * Configures the session to use a specific band. + * + * @param bandId + */ + useBand(bandId) + { + this.band = bandId; + } + // HELPER FUNCTIONS /** From 8deba49522c66ae17d9d8d60e75908c7b49149ff Mon Sep 17 00:00:00 2001 From: mwhitman Date: Thu, 1 Aug 2024 12:35:03 -0400 Subject: [PATCH 06/23] add useBand type declaration --- @types/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/@types/index.d.ts b/@types/index.d.ts index f853620..ee93395 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -411,6 +411,7 @@ export declare interface Session { refresh(callback?: (err: Error) => void): Promise disconnect(callback?: (err: Error) => void): Promise reauthenticate(reauthenticateFn: Function): void + useBand(bandId: string): void } export declare interface ApplicationSession extends Session { From ede749d3303af3f0ce63d8de798de4516d56a198 Mon Sep 17 00:00:00 2001 From: mwhitman Date: Thu, 21 Nov 2024 09:39:35 -0500 Subject: [PATCH 07/23] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b88b01d..76f55e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.24", + "version": "0.2.25", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" From bef7faf7f8b9c4ed33fdbe06840a25b5bdb51fec Mon Sep 17 00:00:00 2001 From: mwhitman Date: Fri, 3 Jan 2025 10:11:08 -0500 Subject: [PATCH 08/23] use built in node fetch if v>=21, otherwise require node-fetch --- src/engines/fetch/engine.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/engines/fetch/engine.js b/src/engines/fetch/engine.js index ddc358b..6959060 100644 --- a/src/engines/fetch/engine.js +++ b/src/engines/fetch/engine.js @@ -1,6 +1,5 @@ var Engine = require("../../engine"); var Helper = require("../../helper"); -var nodeFetch = require("node-fetch"); class FetchEngine extends Engine { @@ -9,14 +8,21 @@ class FetchEngine extends Engine super(config, credentials, storage, options); var self = this; - if (options && options.fetch) { this.fetch = options.fetch } else { - this.fetch = nodeFetch; + const [major, minor, patch] = process.versions.node.split('.').map(Number) + if (major < 21) + { + this.fetch = require("node-fetch");; + } + else + { + this.fetch = fetch; + } } this.doRequest = (options, callback) => From f5055517bee8f0969be8754910a2b9bd1ccfd8ed Mon Sep 17 00:00:00 2001 From: mwhitman Date: Fri, 3 Jan 2025 13:37:50 -0500 Subject: [PATCH 09/23] 0.2.26 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76f55e5..f2efe12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.25", + "version": "0.2.26", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" From a987e38efb02656d60f526c17fc55550b66a6b01 Mon Sep 17 00:00:00 2001 From: Michael Uzquiano Date: Wed, 22 Jan 2025 21:34:52 -0500 Subject: [PATCH 10/23] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e0b2ced..8fc778a 100644 --- a/README.md +++ b/README.md @@ -440,3 +440,14 @@ TODO: how to configure Memory vs Redis ## Custom Cache TODO: how to configure custom caching for JSON responses + +## Documentation + +Please visit: + +https://gitana.io/documentation/gitana/4.0/developers/cookbooks/javascript2.html +https://gitana.io/documentation/gitana/4.0/developers/drivers/javascript.html + +## Support + +For support, please visit https://gitana.io From 4299a55af54ab700743c365ecaa1a3a128227645 Mon Sep 17 00:00:00 2001 From: Michael Uzquiano Date: Sun, 2 Mar 2025 12:20:10 -0500 Subject: [PATCH 11/23] Update README.md Updated readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fc778a..218f52a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # cloudcms-javascript-driver -Updated Cloud CMS JS Engine using modern ECMAScript and Promises +Cloud CMS JavaScript Driver with support for ECMAScript Async, Promises and Callbacks. + +For formal support and assistance, please visit https://gitana.io. ## Installation From dc8cf9a22b6ddc3bb1d587e13a6f53a167c006d5 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Wed, 17 Sep 2025 14:01:00 -0400 Subject: [PATCH 12/23] Added publishNode and unpublishNodes functions, added changeNodeTypeQName function --- src/objects/Node.js | 5 +++- src/session/default/methods/node.js | 45 ++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/objects/Node.js b/src/objects/Node.js index 2beb710..e2d017c 100644 --- a/src/objects/Node.js +++ b/src/objects/Node.js @@ -15,6 +15,7 @@ const NODE_FNS = [ "removeNodeFeature", "refreshNode", "changeNodeQName", + "changeNodeTypeQName", "nodeTree", "resolveNodePath", "resolveNodePaths", @@ -25,7 +26,9 @@ const NODE_FNS = [ "deleteAttachment", "listVersions", "readVersion", - "restoreVersion" + "restoreVersion", + "publishNodes", + "unpublishNodes" ]; class Node extends AbstractObject { diff --git a/src/session/default/methods/node.js b/src/session/default/methods/node.js index 80ed9ea..5cecb03 100644 --- a/src/session/default/methods/node.js +++ b/src/session/default/methods/node.js @@ -420,7 +420,7 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/refresh", {}, callback); } - changeNodeQName(repository, branch, node, newQName) + changeNodeTypeQName(repository, branch, node, newQName) { var repositoryId = this.acquireId(repository); var branchId = this.acquireId(branch); @@ -434,6 +434,20 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/change_qname", params, callback); } + changeNodeType(repository, branch, node, newTypeQName) + { + var repositoryId = this.acquireId(repository); + var branchId = this.acquireId(branch); + var nodeId = this.acquireId(node); + var callback = this.extractOptionalCallback(arguments); + + var params = { + "type": newTypeQName + }; + + return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/change_type", params, callback); + } + /* moveNodesTo(repository, branch, sourceNodes, targetNode, targetPath) { @@ -777,6 +791,35 @@ module.exports = function(Session) return this.post(`/repositories/${repositoryId}/branches/${targetBranchId}/copyfrom/start`, params, body, callback); } + + publishNodes(repository, branch, nodeIds) + { + var repositoryId = this.acquireId(repository); + var branchId = this.acquireId(branch); + var callback = this.extractOptionalCallback(arguments); + + var params = {}; + var body = { + "nodes": nodeIds + }; + + return this.post(`/repositories/${repositoryId}/branches/${branchId}/publish`, params, body, callback); + } + + unpublishNodes(repository, branch, nodeIds) + { + var repositoryId = this.acquireId(repository); + var branchId = this.acquireId(branch); + var callback = this.extractOptionalCallback(arguments); + + var params = {}; + var body = { + "nodes": nodeIds + }; + + return this.post(`/repositories/${repositoryId}/branches/${branchId}/unpublish`, params, body, callback); + } + } return NodeSession; From 4c2762967c9a80637109e5048dc9224a6874b5d6 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 2 Jan 2026 23:35:23 -0500 Subject: [PATCH 13/23] Additional branch + node methods --- package.json | 2 +- src/session/default/methods/branch.js | 45 +++++++++++++++++++++++++++ src/session/default/methods/node.js | 17 ++++++++-- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f2efe12..5d9032c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.26", + "version": "0.2.27", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/branch.js b/src/session/default/methods/branch.js index a50f2a5..d5c7a20 100644 --- a/src/session/default/methods/branch.js +++ b/src/session/default/methods/branch.js @@ -211,6 +211,51 @@ module.exports = function(Session) return this.download(`/repositories/${repositoryId}/branches/${targetBranchId}/changes/export`, params, callback); } + + // merge + + mergeBranch(repository, sourceBranch, targetBranch, options) + { + var repositoryId = this.acquireId(repository); + var sourceBranchId = this.acquireId(sourceBranch); + var targetBranchId = this.acquireId(targetBranch); + var callback = this.extractOptionalCallback(arguments); + + var params = { + "id": sourceBranchId + }; + + var payload = {}; + if (typeof(options) === "object") + { + payload = JSON.parse(JSON.stringify(options)); + } + + return this.post(`/repositories/${repositoryId}/branches/${targetBranchId}/merge`, params, payload, callback); + } + + startMergeBranch(repository, sourceBranch, targetBranch, dryRun) + { + var repositoryId = this.acquireId(repository); + var sourceBranchId = this.acquireId(sourceBranch); + var targetBranchId = this.acquireId(targetBranch); + var callback = this.extractOptionalCallback(arguments); + + if (typeof(dryRun) !== "boolean") + { + dryRun = false; + } + + var params = { + "id": sourceBranchId, + "dryRun": dryRun + }; + + var payload = {}; + + return this.post(`/repositories/${repositoryId}/branches/${targetBranchId}/merge/start`, params, payload, callback); + } + } return BranchSession; diff --git a/src/session/default/methods/node.js b/src/session/default/methods/node.js index 5cecb03..3a17d1d 100644 --- a/src/session/default/methods/node.js +++ b/src/session/default/methods/node.js @@ -420,7 +420,7 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/refresh", {}, callback); } - changeNodeTypeQName(repository, branch, node, newQName) + changeNodeQName(repository, branch, node, newQName) { var repositoryId = this.acquireId(repository); var branchId = this.acquireId(branch); @@ -434,7 +434,7 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId + "/change_qname", params, callback); } - changeNodeType(repository, branch, node, newTypeQName) + changeNodeTypeQName(repository, branch, node, newTypeQName) { var repositoryId = this.acquireId(repository); var branchId = this.acquireId(branch); @@ -792,7 +792,7 @@ module.exports = function(Session) return this.post(`/repositories/${repositoryId}/branches/${targetBranchId}/copyfrom/start`, params, body, callback); } - publishNodes(repository, branch, nodeIds) + publishNodes(repository, branch, nodeIds, options) { var repositoryId = this.acquireId(repository); var branchId = this.acquireId(branch); @@ -803,6 +803,17 @@ module.exports = function(Session) "nodes": nodeIds }; + // copy in any options as parameters + if (options) + { + for (var k in options) + { + if (options[k]) { + params[k] = options[k]; + } + } + } + return this.post(`/repositories/${repositoryId}/branches/${branchId}/publish`, params, body, callback); } From 87a08d9b19935511c4361362fa2114991d32162a Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 2 Jan 2026 23:51:11 -0500 Subject: [PATCH 14/23] Added types --- @types/index.d.ts | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index ee93395..6a7119e 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -437,6 +437,8 @@ export declare interface BranchSession extends Session { startBranchChanges(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, pagination?: Pagination, opts?: BranchChangesOptions, callback?: ResultCb): Promise invalidateBranchChanges(repository: TypedID|string, branch: TypedID|string, callback?: ResultCb): Promise exportBranchChanges(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, view?: BranchChangesView, callback?: ResultCb): Promise + mergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, options?: Object) + startMergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, dryRun?: boolean) } export declare interface DomainSession extends Session { diff --git a/package.json b/package.json index 5d9032c..169773b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.27", + "version": "0.2.28", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" From 7f2262f48eb9b3c1ab0ae02070c6f9116d61d5ee Mon Sep 17 00:00:00 2001 From: uzquiano Date: Sat, 3 Jan 2026 00:31:14 -0500 Subject: [PATCH 15/23] Added pollJob method (4.0) --- @types/index.d.ts | 1 + src/session/default/methods/job.js | 43 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/@types/index.d.ts b/@types/index.d.ts index 6a7119e..675ad61 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -540,6 +540,7 @@ export declare interface JobSession extends Session { killJob(jobId: string|TypedID, callback?: ResultCb): Promise downloadJobAttachment(jobId: string|TypedID, attachmentId?: string, opts?: DownloadJobOptions, callback?: ResultCb): Promise waitForJobCompletion(job: string|TypedID, callback?: ResultCb): Promise + pollJob(jobId: string|TypedID, callback?: ResultCb): Promise } export declare interface TransferSession extends Session { diff --git a/src/session/default/methods/job.js b/src/session/default/methods/job.js index 226dc7f..f600a2d 100644 --- a/src/session/default/methods/job.js +++ b/src/session/default/methods/job.js @@ -38,6 +38,14 @@ module.exports = function(Session) return this.download(`/jobs/${jobId}/attachments/${attachmentId}`, opts, callback); } + pollJob(job) + { + var jobId = this.acquireId(job); + var callback = this.extractOptionalCallback(arguments); + + return this.get("/jobs/" + jobId + "/poll", callback); + } + async waitForJobCompletion(job) { const jobId = this.acquireId(job); @@ -73,6 +81,41 @@ module.exports = function(Session) return job; } + async waitForJobCompletion(job) + { + const jobId = this.acquireId(job); + var callback = this.extractOptionalCallback(arguments); + var self = this; + + const _wait = async () => { + job = await this.readJob(jobId); + if (job.state === "FINISHED") { + return; + } + else if (job.state === "ERROR") { + throw new Error(`Job failed: ${jobId}`) + } + else { + await new Promise(r => setTimeout(r, 1000)); // wait a second + return _wait(); + } + } + + try + { + await _wait(); + } + catch (err) + { + if (callback) callback.call(self, err, job); + throw err; + } + + // in 4.0, it might be better to hand back the job result which is a separate record + if (callback) callback.call(self, null, job); + return job; + } + } return JobSession; From 605c8beef5cc684942f71eda226c1cd1684fd4ed Mon Sep 17 00:00:00 2001 From: uzquiano Date: Sat, 3 Jan 2026 00:31:25 -0500 Subject: [PATCH 16/23] Added pollJob method (4.0) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 169773b..2f05948 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.28", + "version": "0.2.29", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" From f01a25d79d063453cc1ac58e31468bb0a931de6e Mon Sep 17 00:00:00 2001 From: uzquiano Date: Sat, 3 Jan 2026 15:44:25 -0500 Subject: [PATCH 17/23] Added pollForJobCompletion() method, updated tests to run on 4.0 API (for job polling mechanism), updated tests, added editorial methods for starting, transitioning and committing editorial flows, added test --- @types/index.d.ts | 10 +++- README.md | 38 +++++++++++++- package.json | 2 +- src/session/default/methods/editorial.js | 54 +++++++++++++++++++ src/session/default/methods/job.js | 4 +- src/session/default/session.js | 3 +- test/editorial/editorial10.ts | 67 ++++++++++++++++++++++++ test/release/release10.ts | 6 +-- test/transfer/transfer10.ts | 8 +-- test/transfer/transfer20.ts | 8 +-- 10 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 src/session/default/methods/editorial.js create mode 100644 test/editorial/editorial10.ts diff --git a/@types/index.d.ts b/@types/index.d.ts index 675ad61..822f5b9 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -540,6 +540,7 @@ export declare interface JobSession extends Session { killJob(jobId: string|TypedID, callback?: ResultCb): Promise downloadJobAttachment(jobId: string|TypedID, attachmentId?: string, opts?: DownloadJobOptions, callback?: ResultCb): Promise waitForJobCompletion(job: string|TypedID, callback?: ResultCb): Promise + pollForJobCompletion(job: string|TypedID, callback?: ResultCb): Promise pollJob(jobId: string|TypedID, callback?: ResultCb): Promise } @@ -569,6 +570,13 @@ export declare interface ArchiveSession extends Session { downloadArchive(group: string, artifact: string, version: string, vault?: string|TypedID, callback?: ResultCb): Promise } +export declare interface EditorialSession extends Session { + startEditorialFlow(repository: TypedID|string, rootBranch: TypedID|string, operation: string, flowKey: string, properties?: Object, workflowData?: Object, swimlanesObject?: Object, nodeRefArray?:Array): Promise + readCurrentTaskForEditorialFlow(workflowId: string): Promise + transitionEditorialFlow(workflowId: string, route: string): Promise +} + + export declare interface TrackerSession extends Session { trackPage(repositoryId: string, branchId: string, page: PageRendition): void } @@ -603,7 +611,7 @@ export declare interface UtilitySession extends DefaultSession { } -export declare type DefaultSession = ApplicationSession & ArchiveSession & RepositorySession & BranchSession & DomainSession & GraphQLSession & NodeSession & PrincipalSession & ProjectSession & StackSession & WorkflowSession & ChangesetSession & JobSession & TransferSession & TrackerSession & ReleaseSession & PlatformSession; +export declare type DefaultSession = ApplicationSession & ArchiveSession & RepositorySession & BranchSession & DomainSession & GraphQLSession & NodeSession & PrincipalSession & ProjectSession & StackSession & WorkflowSession & ChangesetSession & JobSession & TransferSession & TrackerSession & ReleaseSession & PlatformSession & EditorialSession; diff --git a/README.md b/README.md index 218f52a..3a1e210 100644 --- a/README.md +++ b/README.md @@ -315,6 +315,11 @@ const results: Rows = await session.queryNodes(repositoryId, branchI This library uses Mocha and Chai for testing. To test, first add `gitana.json` to the project root. +This file's `baseURL` should point to a local Gitana API instance running Gitana 4.1 or higher. +Typically, the `baseURL` is set to `http://localhost:8080` for testing. + +Within the Gitana API source, you can run the `DriverTest` test to install driver credentials (which +should be put into `gitana.json`). To run all tests: @@ -322,10 +327,10 @@ To run all tests: npm run alltests ``` -To run a single test (`node`): +To run a single test (`transfer10`): ``` -npm run test node +npx mocha 'test/**/transfer10.ts' --recursive --timeout 120000 --watch-extensions ts,js ``` ## Proxy @@ -443,6 +448,35 @@ TODO: how to configure Memory vs Redis TODO: how to configure custom caching for JSON responses +## Jobs + +Jobs are long-running tasks that are loaded onto Gitana's distributed job engine. They run within +queues and execute in the background. When you send a request to start a job, the request will +return right away with a Job ID that you can use to poll for completion. + +In Gitana 3.2 and before, your code can be written like this: + +``` +var startedJob = await session.startCreateProject({ + title: "My Test Project" +}); +var completedJob = await session.waitForJobCompletion(startedJob._doc); +var projectId = completedJob["created-project-id"]; +``` + +In Gitana 4.0 and beyond, there is a slight difference. You must ues the `pollForJobCompletion` method, +like this: + +``` +var startedJob = await session.startCreateProject({ + title: "My Test Project" +}); +var completedJob = await session.pollForJobCompletion(startedJob._doc); +var projectId = completedJob._result["created-project-id"]; +``` + +The unit tests assume a 4.0 compatible API server. + ## Documentation Please visit: diff --git a/package.json b/package.json index 2f05948..5440673 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.29", + "version": "0.2.30", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/editorial.js b/src/session/default/methods/editorial.js new file mode 100644 index 0000000..4f54d52 --- /dev/null +++ b/src/session/default/methods/editorial.js @@ -0,0 +1,54 @@ +module.exports = function(Session) +{ + class EditorialSession extends Session + { + startEditorialFlow(repository, rootBranch, operation, flowKey, properties, workflowData, swimlanesObject, nodeRefArray) + { + var repositoryId = this.acquireId(repository); + var rootBranchId = this.acquireId(rootBranch); + var callback = this.extractOptionalCallback(arguments); + + var query = {}; + + var payload = {}; + payload.repositoryId = repositoryId; + payload.branchId = rootBranchId; + payload.operation = operation; + payload.flowKey = flowKey; + if (properties) { + payload.properties = properties; + } + if (workflowData) { + payload.workflowData = workflowData; + } + if (swimlanesObject) { + payload.swimlanes = swimlanesObject; + } + if (nodeRefArray) { + payload.nodeRefs = nodeRefArray; + } + + return this.post("/editorial/flows/start", query, payload, callback); + } + + readCurrentTaskForEditorialFlow(workflowId) + { + var callback = this.extractOptionalCallback(arguments); + + return this.get("/editorial/flows/" + workflowId + "/task", {}, callback); + } + + transitionEditorialFlow(workflowId, route) + { + var callback = this.extractOptionalCallback(arguments); + + var query = { + "route": route + }; + + return this.post("/editorial/flows/" + workflowId + "/transition", query, callback); + } + } + + return EditorialSession; +}; \ No newline at end of file diff --git a/src/session/default/methods/job.js b/src/session/default/methods/job.js index f600a2d..32d8390 100644 --- a/src/session/default/methods/job.js +++ b/src/session/default/methods/job.js @@ -81,14 +81,14 @@ module.exports = function(Session) return job; } - async waitForJobCompletion(job) + async pollForJobCompletion(job) { const jobId = this.acquireId(job); var callback = this.extractOptionalCallback(arguments); var self = this; const _wait = async () => { - job = await this.readJob(jobId); + job = await this.pollJob(jobId); if (job.state === "FINISHED") { return; } diff --git a/src/session/default/session.js b/src/session/default/session.js index 67c6747..dbfd5a6 100644 --- a/src/session/default/session.js +++ b/src/session/default/session.js @@ -345,7 +345,8 @@ var modules = [ require("./methods/tracker"), require("./methods/release"), require("./methods/platform"), - require("./methods/archive") + require("./methods/archive"), + require("./methods/editorial") ]; for (var i = 0; i < modules.length; i++) { diff --git a/test/editorial/editorial10.ts b/test/editorial/editorial10.ts new file mode 100644 index 0000000..e73fcd5 --- /dev/null +++ b/test/editorial/editorial10.ts @@ -0,0 +1,67 @@ +import * as CloudCMS from "../.."; + +var assert = require('chai').assert; + +describe('editorial_1', function() { + it('should test editorials methods in a repository without error', async function() { + + var session = await CloudCMS.connect(); + + // create a project + var createProjectJob:any = await session.startCreateProject({ + "title": "test project " + new Date().getTime() + }); + createProjectJob = await session.pollForJobCompletion(createProjectJob); + var projectId = createProjectJob._result["created-project-id"]; + var project = await session.readProject(projectId); + + // find the project repository + var repository:CloudCMS.Repository = await session.readDataStore(project.stackId, "content"); + + // start a new editorial flow + var rootBranchId = "master"; + var operation = "edit"; + var flowKey = "default"; + var properties = { + "title": "My Flow 1" + }; + var workflowData = {}; + var swimlanesObject = {}; + var nodeRefArray = []; + var result:any = await session.startEditorialFlow(repository, rootBranchId, operation, flowKey, properties, workflowData, swimlanesObject, nodeRefArray) + + var workflowId = result.workflowId; + var branchId = result.branchId; + var workspaceId = result.workspaceId; + //var workflowTaskIds = result.workflowTaskIds; + + // read the task for the current user + var currentTask:any = await session.readCurrentTaskForEditorialFlow(workflowId); + //console.log("Current Task: " + JSON.stringify(currentTask)); + assert.isNotNull(currentTask); + var routes = currentTask.routes; // "cancel" and "finish" + assert.isNotNull(routes); + assert.equal(2, Object.keys(routes).length); + + // create three nodes on the flow branch + await session.createNode(repository, branchId, { "title": "n1", "foo": "bar"}); + await session.createNode(repository, branchId, { "title": "n2", "foo": "bar"}); + await session.createNode(repository, branchId, { "title": "n3", "foo": "bar"}); + + // ensure 0 matches on master + var results1 = await session.queryNodes(repository, "master", { "foo": "bar" }) + assert.equal(0, results1.rows.length); + + // finish the flow (this commits back to master branch) + var nextTask = await session.transitionEditorialFlow(workflowId, "finish"); + assert.isNotNull(nextTask); + //console.log("Next Task: " + JSON.stringify(nextTask, null, 2)); + + // wait for things to finish (merge, publish) + await session.sleep(5000); + + // now verify the nodes are on master + var results2 = await session.queryNodes(repository, "master", { "foo": "bar" }) + assert.equal(3, results2.rows.length); + }); +}); \ No newline at end of file diff --git a/test/release/release10.ts b/test/release/release10.ts index a695272..352a68a 100644 --- a/test/release/release10.ts +++ b/test/release/release10.ts @@ -9,9 +9,9 @@ describe('release10', function() { var repository = await session.createRepository(); // Create release - let createJob1 = await session.startCreateRelease(repository, { title: 'Test' }); - createJob1 = await session.waitForJobCompletion(createJob1); - const releaseId1 = createJob1["created-release-id"]; + let createJob1:any = await session.startCreateRelease(repository, { title: 'Test' }); + createJob1 = await session.pollForJobCompletion(createJob1); + const releaseId1 = createJob1._result["created-release-id"]; let release = await session.readRelease(repository, releaseId1); assert.isNotNull(release); diff --git a/test/transfer/transfer10.ts b/test/transfer/transfer10.ts index 120d621..05d7957 100644 --- a/test/transfer/transfer10.ts +++ b/test/transfer/transfer10.ts @@ -12,8 +12,8 @@ describe('transfer10', function () { var now = `${Date.now()}`; var startResult = await session.startCreateProject({title: `test-${now}`}); - var projectJob = await session.waitForJobCompletion(startResult._doc); - var projectId = projectJob["created-project-id"]; + var projectJob = await session.pollForJobCompletion(startResult._doc); + var projectId = projectJob._result["created-project-id"]; var project = await session.readProject(projectId); var repository = await session.readDataStore(project.stackId, "content"); @@ -59,8 +59,8 @@ describe('transfer10', function () { title: `test2-${now}`, projectType: projectType?.reference }); - var projectJob2 = await session.waitForJobCompletion(startResult2._doc); - var project2Id = projectJob2["created-project-id"]; + var projectJob2 = await session.pollForJobCompletion(startResult2._doc); + var project2Id = projectJob2._result["created-project-id"]; var project2 = await session.readProject(project2Id); var repository2 = await session.readDataStore(project2.stackId, "content"); diff --git a/test/transfer/transfer20.ts b/test/transfer/transfer20.ts index 38e8474..8e9fb3b 100644 --- a/test/transfer/transfer20.ts +++ b/test/transfer/transfer20.ts @@ -12,8 +12,8 @@ describe('transfer20', function () { var now = `${Date.now()}`; var startResult = await session.startCreateProject({title: `test-${now}`}); - var projectJob = await session.waitForJobCompletion(startResult._doc); - var projectId = projectJob["created-project-id"]; + var projectJob = await session.pollForJobCompletion(startResult._doc); + var projectId = projectJob._result["created-project-id"]; var project = await session.readProject(projectId); var repository = await session.readDataStore(project.stackId, "content"); @@ -53,8 +53,8 @@ describe('transfer20', function () { var startResult2 = await session.startCreateProject({ title: `test2-${now}`, }); - var projectJob2 = await session.waitForJobCompletion(startResult2._doc); - var project2Id = projectJob2["created-project-id"]; + var projectJob2 = await session.pollForJobCompletion(startResult2._doc); + var project2Id = projectJob2._result["created-project-id"]; var project2 = await session.readProject(project2Id); var repository2 = await session.readDataStore(project2.stackId, "content"); From 797a3e33d773de4aee6d605bc835058e8e161e21 Mon Sep 17 00:00:00 2001 From: mwhitman Date: Fri, 9 Jan 2026 13:31:02 -0500 Subject: [PATCH 18/23] return types, repository methods, only apply default qs when not overriden, update version --- @types/index.d.ts | 6 +++-- package.json | 2 +- src/session/default/methods/repository.js | 32 +++++++++++++++++++++++ src/session/default/session.js | 5 +++- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 822f5b9..ffe462a 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -422,6 +422,8 @@ export declare interface RepositorySession extends Session { buildRepositoryReference(repository: TypedID|string, callback?: ResultCb): Promise createRepository(obj?: Object, callback?: ResultCb): Promise queryRepositories(query?: Object, pagination?: Pagination, callback?: ResultCb>): Promise> + readRepository(repository: TypedID|string, callback?: ResultCb): Promise + updateRepository(repository: TypedID|string, obj: Object, callback?: ResultCb): Promise } export declare interface BranchSession extends Session { @@ -437,8 +439,8 @@ export declare interface BranchSession extends Session { startBranchChanges(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, pagination?: Pagination, opts?: BranchChangesOptions, callback?: ResultCb): Promise invalidateBranchChanges(repository: TypedID|string, branch: TypedID|string, callback?: ResultCb): Promise exportBranchChanges(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, view?: BranchChangesView, callback?: ResultCb): Promise - mergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, options?: Object) - startMergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, dryRun?: boolean) + mergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, options?: Object, callback?: ResultCb): Promise + startMergeBranch(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, dryRun?: boolean, callback?: ResultCb): Promise } export declare interface DomainSession extends Session { diff --git a/package.json b/package.json index 5440673..9f349f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.30", + "version": "0.2.31", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/repository.js b/src/session/default/methods/repository.js index ee24719..39b91ef 100644 --- a/src/session/default/methods/repository.js +++ b/src/session/default/methods/repository.js @@ -29,6 +29,38 @@ module.exports = function(Session) return this.post("/repositories/query", pagination, query, callback); }; + /** + * Read a repository. + * + * @param repository + * + * @returns {*} + */ + readRepository(repository) + { + var repositoryId = this.acquireId(repository); + var callback = this.extractOptionalCallback(arguments); + + return this.get("/repositories/" + repositoryId, {}, callback); + }; + + /** + * Updates a repository. + * + * @param repository + * @param obj + * + * @returns {*} + * + */ + updateRepository(repository, obj) + { + var repositoryId = this.acquireId(repository); + var callback = this.extractOptionalCallback(arguments); + + return this.put("/repositories/" + repositoryId, {}, obj, callback); + } + } return RepositorySession; diff --git a/src/session/default/session.js b/src/session/default/session.js index dbfd5a6..8146df5 100644 --- a/src/session/default/session.js +++ b/src/session/default/session.js @@ -83,7 +83,10 @@ class DefaultSession for (var k in this.defaults.qs) { - qs[k] = this.defaults.qs[k]; + if (!(k in qs)) + { + qs[k] = this.defaults.qs[k]; + } } // pass band via query string? From 9d93a39225f53953701368032585fea4dd7cb434 Mon Sep 17 00:00:00 2001 From: mwhitman Date: Fri, 9 Jan 2026 13:31:58 -0500 Subject: [PATCH 19/23] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f349f8..5b53175 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.31", + "version": "0.2.32", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" From cad5959145e81e66561b9986c37bdcd2051e6233 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 9 Jan 2026 15:17:17 -0500 Subject: [PATCH 20/23] Early work on sidekick methods --- @types/index.d.ts | 26 ++++- package.json | 2 +- src/session/default/methods/node.js | 4 +- src/session/default/methods/sidekick.js | 149 ++++++++++++++++++++++++ src/session/default/session.js | 3 +- test/sidekick/sidekick10.ts | 14 +++ 6 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/session/default/methods/sidekick.js create mode 100644 test/sidekick/sidekick10.ts diff --git a/@types/index.d.ts b/@types/index.d.ts index ffe462a..28629fd 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -295,6 +295,18 @@ export declare interface Domain extends DataStore { // queryPrincipals(query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> } +export declare interface Assistant extends PlatformObject +{ +} + +export declare interface AssistantSession extends PlatformObject +{ +} + +export declare interface AssistantMessage extends PlatformObject +{ +} + export declare interface Branch extends RepositoryObject { root: string tip: string @@ -475,7 +487,7 @@ export declare interface NodeSession extends Session { unassociateChild(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, childNode: TypedID|string, callback?: ResultCb): Promise deleteNode(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, callback?: ResultCb): Promise updateNode(repository: TypedID|string, branch: TypedID|string, node: T & TypedID, callback?: ResultCb): Promise - patchNode(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, patchObject: Object, callback?: ResultCb): Promise + patchNode(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, patchArray: Array, callback?: ResultCb): Promise addNodeFeature(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, featureId: string, config?: Object, callback?: ResultCb): Promise removeNodeFeature(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, featureId: string, callback?: ResultCb): Promise refreshNode(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, callback?: ResultCb): Promise @@ -495,6 +507,18 @@ export declare interface NodeSession extends Session { startCopyNodes(repository: TypedID|string, sourceBranch: TypedID|string, targetBranch: TypedID|string, nodeIds: Array, config?: SyncNodesConfig, callback?: ResultCb): Promise } +export declare interface SidekickSession extends Session { + readAssistant(assistant: TypedID|string, callback?: ResultCb): Promise + queryAssistants(query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> + createAssistant(obj?: Object, callback?: ResultCb): Promise + deleteAssistant(assistant: TypedID|string, callback?: ResultCb): Promise + updateAssistant(assistant: Assistant, callback?: ResultCb): Promise + + openAssistantSession(obj: object): Promise + startAssistantSessionRequest(assistant: AssistantSession, obj: string, callback?: ResultCb): Promise + readAssistantSessionMessage(assistant: AssistantSession, assistantMessageId: string, callback?: ResultCb): Promise +} + export declare interface PrincipalSession extends Session { readPrincipal(domain: TypedID|string, principalId: string, callback?: ResultCb): Promise queryPrincipals(domain: TypedID|string, query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> diff --git a/package.json b/package.json index 5b53175..9f349f8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.32", + "version": "0.2.31", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/node.js b/src/session/default/methods/node.js index 3a17d1d..a15bf7b 100644 --- a/src/session/default/methods/node.js +++ b/src/session/default/methods/node.js @@ -380,14 +380,14 @@ module.exports = function(Session) return this.put(`/repositories/${repositoryId}/branches/${branchId}/nodes`, {}, payload, callback); } - patchNode(repository, branch, node, patchObject) + patchNode(repository, branch, node, patchArray) { var repositoryId = this.acquireId(repository); var branchId = this.acquireId(branch); var nodeId = this.acquireId(node); var callback = this.extractOptionalCallback(arguments); - return this.patch("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId, {}, patchObject, callback); + return this.patch("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/" + nodeId, {}, patchArray, callback); } addNodeFeature(repository, branch, node, featureId, config) diff --git a/src/session/default/methods/sidekick.js b/src/session/default/methods/sidekick.js new file mode 100644 index 0000000..6631a95 --- /dev/null +++ b/src/session/default/methods/sidekick.js @@ -0,0 +1,149 @@ +module.exports = function(Session) +{ + class SidekickSession extends Session + { + // async buildSidekickReference(sidekick) + // { + // var sidekickId = this.acquireId(sidekick); + // var callback = this.extractOptionalCallback(arguments); + // + // var platformId = await this.getPlatformId(); + // var ref = `node://${platformId}/${sidekickId}`; + // if (callback) + // { + // callback(ref); + // } + // + // return ref; + // } + + /** + * Reads an assistant (sidekick). + * + * @param assistant + * @returns {*} + */ + readAssistant(assistant) + { + var sidekickId = this.acquireId(assistant); + var callback = this.extractOptionalCallback(arguments); + + var qs = {}; + + return this.get("/sidekicks/" + sidekickId, qs, callback); + } + + /** + * Queries for assistants (sidekicks). + * + * @param query + * @param pagination + * @returns {*} + */ + queryAssistants(query, pagination) + { + var callback = this.extractOptionalCallback(arguments); + + return this.post("/sidekicks/query", pagination, query, callback); + } + + /** + * Creates an assistant (sidekick). + * + * @param obj + * @returns {*} + */ + createAssistant(obj) + { + var callback = this.extractOptionalCallback(arguments); + + var qs = {}; + + return this.post("/sidekicks", qs, obj, callback); + } + + /** + * Deletes an assistant (sidekick). + * + * @param assistant + * @returns {*} + */ + deleteAssistant(assistant) + { + var sidekickId = this.acquireId(assistant); + var callback = this.extractOptionalCallback(arguments); + + return this.del("/sidekicks/" + sidekickId, {}, callback); + } + + /** + * Updates an assistant. + * + * @param assistant + * @returns {*} + */ + updateAssistant(assistant) + { + var sidekickId = this.acquireId(assistant); + var callback = this.extractOptionalCallback(arguments); + + return this.put("/sidekicks/" + sidekickId, {}, assistant, callback); + } + + + + //////////////////// + + /** + * Opens an assistant session + * + * @param obj + * @returns {*} + */ + openAssistantSession(obj) + { + var callback = this.extractOptionalCallback(arguments); + + var qs = {}; + + return this.post("/oneteam/sidekicks/session/open", qs, obj, callback); + } + + /** + * Sends a request to an assistant session. + * + * @param assistantSession + * @param obj + * @returns {*} + */ + startAssistantSessionRequest(assistantSession, obj) + { + var assistantSessionId = this.acquireId(assistantSession); + var callback = this.extractOptionalCallback(arguments); + + var qs = {}; + + return this.post("/oneteam/sidekicks/sessions/" + assistantSessionId + "/request", qs, obj, callback); + } + + /** + * Reads an assistant session message + * + * @param assistantSession + * @param assistantMessageId + * + * @returns {*} + */ + readAssistantSessionMessage(assistantSession, assistantMessageId) + { + var assistantSessionId = this.acquireId(assistantSession); + var callback = this.extractOptionalCallback(arguments); + + var qs = {}; + + return this.get("/oneteam/sidekicks/sessions/" + assistantSessionId + "/messages/" + assistantMessageId, qs, callback); + } + } + + return SidekickSession; +}; \ No newline at end of file diff --git a/src/session/default/session.js b/src/session/default/session.js index 8146df5..19b6762 100644 --- a/src/session/default/session.js +++ b/src/session/default/session.js @@ -349,7 +349,8 @@ var modules = [ require("./methods/release"), require("./methods/platform"), require("./methods/archive"), - require("./methods/editorial") + require("./methods/editorial"), + //require("./methods/sidekick") ]; for (var i = 0; i < modules.length; i++) { diff --git a/test/sidekick/sidekick10.ts b/test/sidekick/sidekick10.ts new file mode 100644 index 0000000..26a45c6 --- /dev/null +++ b/test/sidekick/sidekick10.ts @@ -0,0 +1,14 @@ +import * as CloudCMS from "../.."; + +var assert = require('chai').assert; + +describe('sidekick_1', function() { + it('should test sidekick methods in a repository without error', async function() { + + //var session = await CloudCMS.connect(); + + // create a sidekick + //var sidekick1 = await session.createSidekick() + + }); +}); \ No newline at end of file From 772125c369fbb663ee0dfc6246c73477c615bff0 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Tue, 13 Jan 2026 12:09:49 -0500 Subject: [PATCH 21/23] Added agentic methods for sidekicks, updated readme with test info --- @types/index.d.ts | 24 +++++----- README.md | 6 ++- package.json | 2 +- src/session/default/methods/sidekick.js | 64 ++++++++++++------------- src/session/default/session.js | 2 +- test/sidekick/sidekick10.ts | 33 +++++++++++-- 6 files changed, 80 insertions(+), 51 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 28629fd..26e96e7 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -295,15 +295,15 @@ export declare interface Domain extends DataStore { // queryPrincipals(query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> } -export declare interface Assistant extends PlatformObject +export declare interface Agent extends PlatformObject { } -export declare interface AssistantSession extends PlatformObject +export declare interface AgentSession extends PlatformObject { } -export declare interface AssistantMessage extends PlatformObject +export declare interface AgentMessage extends PlatformObject { } @@ -508,15 +508,15 @@ export declare interface NodeSession extends Session { } export declare interface SidekickSession extends Session { - readAssistant(assistant: TypedID|string, callback?: ResultCb): Promise - queryAssistants(query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> - createAssistant(obj?: Object, callback?: ResultCb): Promise - deleteAssistant(assistant: TypedID|string, callback?: ResultCb): Promise - updateAssistant(assistant: Assistant, callback?: ResultCb): Promise + readAgent(agent: TypedID|string, callback?: ResultCb): Promise + queryAgents(query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> + createAgent(obj?: Object, callback?: ResultCb): Promise + deleteAgent(agent: TypedID|string, callback?: ResultCb): Promise + updateAgent(agent: Agent, callback?: ResultCb): Promise - openAssistantSession(obj: object): Promise - startAssistantSessionRequest(assistant: AssistantSession, obj: string, callback?: ResultCb): Promise - readAssistantSessionMessage(assistant: AssistantSession, assistantMessageId: string, callback?: ResultCb): Promise + openAgentSession(obj: object, callback?: ResultCb): Promise + startAgentSessionRequest(agent: AgentSession, obj: string, callback?: ResultCb): Promise + readAgentSessionMessage(agent: AgentSession, agentMessageId: string, callback?: ResultCb): Promise } export declare interface PrincipalSession extends Session { @@ -637,7 +637,7 @@ export declare interface UtilitySession extends DefaultSession { } -export declare type DefaultSession = ApplicationSession & ArchiveSession & RepositorySession & BranchSession & DomainSession & GraphQLSession & NodeSession & PrincipalSession & ProjectSession & StackSession & WorkflowSession & ChangesetSession & JobSession & TransferSession & TrackerSession & ReleaseSession & PlatformSession & EditorialSession; +export declare type DefaultSession = ApplicationSession & ArchiveSession & RepositorySession & BranchSession & DomainSession & GraphQLSession & NodeSession & PrincipalSession & ProjectSession & StackSession & WorkflowSession & ChangesetSession & JobSession & TransferSession & TrackerSession & ReleaseSession & PlatformSession & EditorialSession & SidekickSession; diff --git a/README.md b/README.md index 3a1e210..05624a4 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,10 @@ To run a single test (`transfer10`): npx mocha 'test/**/transfer10.ts' --recursive --timeout 120000 --watch-extensions ts,js ``` +The unit tests assume a 4.0 compatible API server. +Drop your `gitana.json` file into the project root folder. + + ## Proxy Configure the driver to use an HTTP or HTTPS proxy using the following environment variables to specify @@ -475,8 +479,6 @@ var completedJob = await session.pollForJobCompletion(startedJob._doc); var projectId = completedJob._result["created-project-id"]; ``` -The unit tests assume a 4.0 compatible API server. - ## Documentation Please visit: diff --git a/package.json b/package.json index 9f349f8..385bb1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.31", + "version": "0.2.33", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/sidekick.js b/src/session/default/methods/sidekick.js index 6631a95..c7769ba 100644 --- a/src/session/default/methods/sidekick.js +++ b/src/session/default/methods/sidekick.js @@ -18,29 +18,29 @@ module.exports = function(Session) // } /** - * Reads an assistant (sidekick). + * Reads an agent (sidekick). * - * @param assistant + * @param agent * @returns {*} */ - readAssistant(assistant) + readAgent(agent) { - var sidekickId = this.acquireId(assistant); + var agentId = this.acquireId(agent); var callback = this.extractOptionalCallback(arguments); var qs = {}; - return this.get("/sidekicks/" + sidekickId, qs, callback); + return this.get("/sidekicks/" + agentId, qs, callback); } /** - * Queries for assistants (sidekicks). + * Queries for agents (sidekicks). * * @param query * @param pagination * @returns {*} */ - queryAssistants(query, pagination) + queryAgents(query, pagination) { var callback = this.extractOptionalCallback(arguments); @@ -48,12 +48,12 @@ module.exports = function(Session) } /** - * Creates an assistant (sidekick). + * Creates an agent (sidekick). * * @param obj * @returns {*} */ - createAssistant(obj) + createAgent(obj) { var callback = this.extractOptionalCallback(arguments); @@ -63,31 +63,31 @@ module.exports = function(Session) } /** - * Deletes an assistant (sidekick). + * Deletes an agent (sidekick). * - * @param assistant + * @param agent * @returns {*} */ - deleteAssistant(assistant) + deleteAgent(agent) { - var sidekickId = this.acquireId(assistant); + var agentId = this.acquireId(agent); var callback = this.extractOptionalCallback(arguments); - return this.del("/sidekicks/" + sidekickId, {}, callback); + return this.del("/sidekicks/" + agentId, {}, callback); } /** - * Updates an assistant. + * Updates an agent. * - * @param assistant + * @param agent * @returns {*} */ - updateAssistant(assistant) + updateAgent(agent) { - var sidekickId = this.acquireId(assistant); + var agentId = this.acquireId(agent); var callback = this.extractOptionalCallback(arguments); - return this.put("/sidekicks/" + sidekickId, {}, assistant, callback); + return this.put("/sidekicks/" + agentId, {}, agent, callback); } @@ -95,12 +95,12 @@ module.exports = function(Session) //////////////////// /** - * Opens an assistant session + * Opens an agent session * * @param obj * @returns {*} */ - openAssistantSession(obj) + openAgentSession(obj) { var callback = this.extractOptionalCallback(arguments); @@ -110,38 +110,38 @@ module.exports = function(Session) } /** - * Sends a request to an assistant session. + * Sends a request to an agent session. * - * @param assistantSession + * @param agentSession * @param obj * @returns {*} */ - startAssistantSessionRequest(assistantSession, obj) + startAgentSessionRequest(agentSession, obj) { - var assistantSessionId = this.acquireId(assistantSession); + var agentSessionId = this.acquireId(agentSession); var callback = this.extractOptionalCallback(arguments); var qs = {}; - return this.post("/oneteam/sidekicks/sessions/" + assistantSessionId + "/request", qs, obj, callback); + return this.post("/oneteam/sidekicks/sessions/" + agentSessionId + "/request", qs, obj, callback); } /** - * Reads an assistant session message + * Reads an agent session message * - * @param assistantSession - * @param assistantMessageId + * @param agentSession + * @param agentMessageId * * @returns {*} */ - readAssistantSessionMessage(assistantSession, assistantMessageId) + readAgentSessionMessage(agentSession, agentMessageId) { - var assistantSessionId = this.acquireId(assistantSession); + var agentSessionId = this.acquireId(agentSession); var callback = this.extractOptionalCallback(arguments); var qs = {}; - return this.get("/oneteam/sidekicks/sessions/" + assistantSessionId + "/messages/" + assistantMessageId, qs, callback); + return this.get("/oneteam/sidekicks/sessions/" + agentSessionId + "/messages/" + agentMessageId, qs, callback); } } diff --git a/src/session/default/session.js b/src/session/default/session.js index 19b6762..5b4b4c5 100644 --- a/src/session/default/session.js +++ b/src/session/default/session.js @@ -350,7 +350,7 @@ var modules = [ require("./methods/platform"), require("./methods/archive"), require("./methods/editorial"), - //require("./methods/sidekick") + require("./methods/sidekick") ]; for (var i = 0; i < modules.length; i++) { diff --git a/test/sidekick/sidekick10.ts b/test/sidekick/sidekick10.ts index 26a45c6..3acc375 100644 --- a/test/sidekick/sidekick10.ts +++ b/test/sidekick/sidekick10.ts @@ -5,10 +5,37 @@ var assert = require('chai').assert; describe('sidekick_1', function() { it('should test sidekick methods in a repository without error', async function() { - //var session = await CloudCMS.connect(); + var session = await CloudCMS.connect(); - // create a sidekick - //var sidekick1 = await session.createSidekick() + // create an agent + var agent1 = await session.createAgent({ + "fa": 1 + }); + // query for agents + var r1 = await session.queryAgents({ + "fa": 1 + }); + assert.equal(1, r1.rows.length); + + // read back agent + var agent2 = await session.readAgent(agent1._doc); + assert.isNotNull(agent2); + + // update the agent + agent2["fo"] = "foo"; + await session.updateAgent(agent2); + + // verify the update worked + var agent3 = await session.readAgent(agent2._doc); + assert.isNotNull(agent3); + assert.equal("foo", agent2["fo"]); + + // delete the agent + await session.deleteAgent(agent3); + + // verify the delete worked + var agent4 = await session.readAgent(agent3._doc); + assert.isNull(agent4); }); }); \ No newline at end of file From 73e72aee819022038d68a8d18499e0bda1818b81 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 13 Feb 2026 13:15:04 -0500 Subject: [PATCH 22/23] Fix to exportProject method, version bump --- package.json | 2 +- src/session/default/methods/transfer.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 385bb1e..b27045e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.33", + "version": "0.2.34", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/session/default/methods/transfer.js b/src/session/default/methods/transfer.js index 662189b..02645c7 100644 --- a/src/session/default/methods/transfer.js +++ b/src/session/default/methods/transfer.js @@ -81,6 +81,8 @@ module.exports = function(Session) async exportProject(project, opts, configuration, callback) { + var self = this; + var callback = this.extractOptionalCallback(arguments); var projectRef = await this.buildProjectReference(project); From 971e8bab9007e8cf09a5ef2813f2ed2dabe87ce8 Mon Sep 17 00:00:00 2001 From: uzquiano Date: Fri, 8 May 2026 11:04:50 -0400 Subject: [PATCH 23/23] Remove access_token from query string, updates to support OAuth 2.1 in Gitana 4.1 --- @types/index.d.ts | 2 ++ package.json | 2 +- src/engine.js | 15 ++++++++++---- src/engines/axios/engine.js | 2 +- src/engines/fetch/engine.js | 2 +- src/objects/Application.js | 1 + src/session/default/methods/job.js | 7 ++++--- src/session/default/methods/node.js | 32 ++++++++++++++++++++++++++++- 8 files changed, 52 insertions(+), 11 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 26e96e7..b94fd10 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -475,6 +475,7 @@ export declare interface NodeSession extends Session { createNode(repository: TypedID|string, branch: TypedID|string, obj?: Object, options?: Object, callback?: ResultCb): Promise deleteNodes(repository: TypedID|string, branch: TypedID|string, nodes: string|Array|Array, callback?: ResultCb): Promise updateNodes(repository: TypedID|string, branch: TypedID|string, nodes: Array, callback?: ResultCb): Promise + createNodes(repository: TypedID|string, branch: TypedID|string, nodes: Array, callback?: ResultCb): Promise queryNodeRelatives(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, associationTypeQName: string, associationDirection: string, query?: Object, pagination?: Pagination, callback?: ResultCb>): Promise> queryNodeChildren(repository: TypedID|string, branch: TypedID|string, node: TypedID|string, query: Object, pagination?: Pagination, callback?: ResultCb>): Promise> @@ -618,6 +619,7 @@ export declare interface ReleaseSession extends Session { export declare interface PlatformSession extends Session { readPlatform(callback?: ResultCb): Promise + readAuthInfo(callback?: ResultCb): Promise getPlatformId(callback?: ResultCb): Promise buildPlatformReference(callback?: ResultCb): Promise } diff --git a/package.json b/package.json index b27045e..ac70ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudcms", - "version": "0.2.34", + "version": "0.2.38", "repository": { "type": "git", "url": "git://github.com/gitana/cloudcms-javascript-driver.git" diff --git a/src/engine.js b/src/engine.js index 90d1c39..8a7b0f7 100644 --- a/src/engine.js +++ b/src/engine.js @@ -611,16 +611,23 @@ class Engine requestObject.headers = requestObject.headers || {} - if (tokenType === 'bearer' && !requestObject.headers.Authorization) { + if (tokenType && tokenType.toLowerCase() === 'bearer' && !requestObject.headers.Authorization) + { requestObject.headers.Authorization = 'Bearer ' + accessToken - } else { + } + else + { var parts = requestObject.url.split('#'); - var token = 'access_token=' + accessToken; + //var token = 'access_token=' + accessToken; + var token = ''; var url = parts[0].replace(/[?&]access_token=[^&#]/, ''); var fragment = parts[1] ? '#' + parts[1] : ''; // Prepend the correct query string parameter to the url. - requestObject.url = url + (url.indexOf('?') > -1 ? '&' : '?') + token + fragment; + if (token || fragment) + { + requestObject.url = url + (url.indexOf('?') > -1 ? '&' : '?') + token + fragment; + } // Attempt to avoid storing the url in proxies, since the access token // is exposed in the query parameters. diff --git a/src/engines/axios/engine.js b/src/engines/axios/engine.js index c303333..ed15490 100644 --- a/src/engines/axios/engine.js +++ b/src/engines/axios/engine.js @@ -314,7 +314,7 @@ class AxiosEngine extends Engine if (payload) { options.headers["Content-Type"] = "application/json"; - options.data = JSON.stringify(data); + options.data = JSON.stringify(payload); } if (headers) diff --git a/src/engines/fetch/engine.js b/src/engines/fetch/engine.js index 6959060..8bb1412 100644 --- a/src/engines/fetch/engine.js +++ b/src/engines/fetch/engine.js @@ -348,7 +348,7 @@ class FetchEngine extends Engine if (payload) { options.headers["Content-Type"] = "application/json"; - options.body = JSON.stringify(data); + options.body = JSON.stringify(payload); } if (headers) diff --git a/src/objects/Application.js b/src/objects/Application.js index a8e0dd4..04cc52c 100644 --- a/src/objects/Application.js +++ b/src/objects/Application.js @@ -8,6 +8,7 @@ class Application extends AbstractObject constructor(session, obj) { super(); + Object.assign(this, obj); this.session = session; diff --git a/src/session/default/methods/job.js b/src/session/default/methods/job.js index 32d8390..1f7f37c 100644 --- a/src/session/default/methods/job.js +++ b/src/session/default/methods/job.js @@ -38,7 +38,7 @@ module.exports = function(Session) return this.download(`/jobs/${jobId}/attachments/${attachmentId}`, opts, callback); } - pollJob(job) + async pollJob(job) { var jobId = this.acquireId(job); var callback = this.extractOptionalCallback(arguments); @@ -83,12 +83,13 @@ module.exports = function(Session) async pollForJobCompletion(job) { + var self = this; + const jobId = this.acquireId(job); var callback = this.extractOptionalCallback(arguments); - var self = this; const _wait = async () => { - job = await this.pollJob(jobId); + job = await self.pollJob(jobId); if (job.state === "FINISHED") { return; } diff --git a/src/session/default/methods/node.js b/src/session/default/methods/node.js index a15bf7b..3a02bca 100644 --- a/src/session/default/methods/node.js +++ b/src/session/default/methods/node.js @@ -141,7 +141,6 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes/find", pagination, config, callback); } - /** * Creates a node. @@ -193,6 +192,37 @@ module.exports = function(Session) return this.post("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes", qs, obj, callback); } + /** + * Creates multiple nodes. + * + * @param repository + * @param branch + * @param objects + * @returns {*} + */ + createNodes(repository, branch, objects) + { + var repositoryId = this.acquireId(repository); + var branchId = this.acquireId(branch); + var callback = this.extractOptionalCallback(arguments); + + var formData = new FormData(); + + for (var i = 0; i < objects.length; i++) + { + var obj = objects[i]; + + formData.append("node-" + i, obj, { + "contentType": "application/json", + "filename": "node-i" + i + }); + } + + var qs = {}; + + return this.multipartPost("/repositories/" + repositoryId + "/branches/" + branchId + "/nodes", qs, formData, callback); + } + queryNodeRelatives(repository, branch, node, associationTypeQName, associationDirection, query, pagination) { var repositoryId = this.acquireId(repository);