Skip to content

Commit

Permalink
Prep shared API URL util for use on external sites
Browse files Browse the repository at this point in the history
refs #5942, #6150

There were a few key problems I was looking to solve with this:

- Introduce a single point of truth for what the URL for accessing the API should be
- Provide a simple way to configure the utility (much like a true SDK)

As of this commit, this utility is still automatically available in a Ghost theme.
To use it on an external site, the code would look like:

```
<script type="text/javascript" src="http://my-ghost-blog.com/shared/ghost-url.min.js"></script>
<script type="text/javascript">
ghost.init({
   clientId: "<your-client-id>",
   clientSecret: "<your-client-secret>"
});
</script>
```

To achieve this, there have been a number of changes:

- A new `apiUrl` function has been added to config, which calculates the correct URL. This needs to be unified with the other url generation functions as a separate piece of work.
- The serveSharedFile middleware has been updated, so that it can serve files from / or /shared and to substitute `{{api-url}}` as it does `{{blog-url}}`.
- ghost-url.js and ghost-url.min.js have been updated to be served via the serveSharedFile middleware
- ghost-url.js has been changed slightly, to take the url from an inline variable which is substituted the first time it is served
- `{{ghost_head}}` has been updated, removing the api url handling which is now in config/url.js and removing the configuration of the utility in favour of calling `init()` after the script is required
- `{{ghost_head}}` has also had the meta tags for client id and secret removed
- tests have been updated
  • Loading branch information
ErisDS committed Dec 15, 2015
1 parent 9eb065d commit 9eadeb9
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 211 deletions.
1 change: 1 addition & 0 deletions core/server/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function ConfigManager(config) {
this.urlJoin = configUrl.urlJoin;
this.urlFor = configUrl.urlFor;
this.urlPathForPost = configUrl.urlPathForPost;
this.apiUrl = configUrl.apiUrl;

// If we're given an initial config object then we can set it.
if (config && _.isObject(config)) {
Expand Down
24 changes: 22 additions & 2 deletions core/server/config/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

var moment = require('moment'),
_ = require('lodash'),
ghostConfig = '';
ghostConfig = '',
// @TODO: unify this with routes.apiBaseUrl
apiPath = '/ghost/api/v0.1';

// ## setConfig
// Simple utility function to allow
Expand Down Expand Up @@ -146,7 +148,7 @@ function urlFor(context, data, absolute) {
knownPaths = {
home: '/',
rss: '/rss/',
api: '/ghost/api/v0.1',
api: apiPath,
sitemap_xsl: '/sitemap.xsl'
};

Expand Down Expand Up @@ -218,7 +220,25 @@ function urlFor(context, data, absolute) {
return createUrl(urlPath, absolute, secure);
}

function apiUrl() {
// @TODO unify this with urlFor
var url;

if (ghostConfig.forceAdminSSL) {
url = (ghostConfig.urlSSL || ghostConfig.url).replace(/^.*?:\/\//g, 'https://');
} else if (ghostConfig.urlSSL) {
url = ghostConfig.urlSSL.replace(/^.*?:\/\//g, 'https://');
} else if (ghostConfig.url.match(/^https:/)) {
url = ghostConfig.url;
} else {
url = ghostConfig.url.replace(/^.*?:\/\//g, '//');
}

return url.replace(/\/$/, '') + apiPath + '/';
}

module.exports.setConfig = setConfig;
module.exports.urlJoin = urlJoin;
module.exports.urlFor = urlFor;
module.exports.urlPathForPost = urlPathForPost;
module.exports.apiUrl = apiUrl;
34 changes: 8 additions & 26 deletions core/server/helpers/ghost_head.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,30 +278,14 @@ function finaliseSchema(schema, head) {
}

function getAjaxHelper(clientId, clientSecret) {
var apiPath = require('../routes').apiBaseUri,
url, useOrigin;

if (config.forceAdminSSL) {
url = 'https://' + (config.urlSSL || config.url).replace(/.*?:\/\//g, '').replace(/\/$/, '') + apiPath;
useOrigin = false;
} else {
url = config.paths.subdir + apiPath;
useOrigin = true;
}

return '<script type="text/javascript">\n' +
'window.ghost = window.ghost || {};\n' +
'window.ghost.config = {\n' +
'\turl: \'' + url + '\',\n' +
'\tuseOrigin: ' + (useOrigin ? 'true' : 'false') + ',\n' +
'\torigin: window.location.origin,\n' +
'\tclientId: \'' + clientId + '\',\n' +
'\tclientSecret: \'' + clientSecret + '\'\n' +
'};' +
'</script>' +
'<script type="text/javascript" src="' +
assetHelper('shared/ghost-url.js', {hash: {minifyInProduction: true}}) +
'"></script>';
return '<script type="text/javascript" src="' +
assetHelper('shared/ghost-url.js', {hash: {minifyInProduction: true}}) + '"></script>\n' +
'<script type="text/javascript">\n' +
'ghost.init({\n' +
'\tclientId: "' + clientId + '",\n' +
'\tclientSecret: "' + clientSecret + '"\n' +
'});\n' +
'</script>';
}

ghost_head = function (options) {
Expand Down Expand Up @@ -364,8 +348,6 @@ ghost_head = function (options) {
}

if (metaData.clientId && metaData.clientSecret) {
head.push(writeMetaTag('ghost:client_id', metaData.clientId));
head.push(writeMetaTag('ghost:client_secret', metaData.clientSecret));
head.push(getAjaxHelper(metaData.clientId, metaData.clientSecret));
}
}
Expand Down
4 changes: 4 additions & 0 deletions core/server/middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ setupMiddleware = function setupMiddleware(blogApp, adminApp) {
// Favicon
blogApp.use(serveSharedFile('favicon.ico', 'image/x-icon', utils.ONE_DAY_S));

// Ghost-Url
blogApp.use(serveSharedFile('shared/ghost-url.js', 'application/javascript', utils.ONE_HOUR_S));
blogApp.use(serveSharedFile('shared/ghost-url.min.js', 'application/javascript', utils.ONE_HOUR_S));

// Static assets
blogApp.use('/shared', express.static(path.join(corePath, '/shared'), {maxAge: utils.ONE_HOUR_MS}));
blogApp.use('/content/images', storage.getStorage().serve());
Expand Down
16 changes: 11 additions & 5 deletions core/server/middleware/serve-shared-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ var crypto = require('crypto'),
// Handles requests to robots.txt and favicon.ico (and caches them)
function serveSharedFile(file, type, maxAge) {
var content,
filePath = path.join(config.paths.corePath, 'shared', file),
re = /(\{\{blog-url\}\})/g;
corePath = config.paths.corePath,
filePath,
blogRegex = /(\{\{blog-url\}\})/g,
apiRegex = /(\{\{api-url\}\})/g;

filePath = file.match(/^shared/) ? path.join(corePath, file) : path.join(corePath, 'shared', file);

return function serveSharedFile(req, res, next) {
if (req.url === '/' + file) {
if (req.path === '/' + file) {
if (content) {
res.writeHead(200, content.headers);
res.end(content.body);
Expand All @@ -20,8 +24,10 @@ function serveSharedFile(file, type, maxAge) {
if (err) {
return next(err);
}
if (type === 'text/xsl' || type === 'text/plain') {
buf = buf.toString().replace(re, config.url.replace(/\/$/, ''));

if (type === 'text/xsl' || type === 'text/plain' || type === 'application/javascript') {
buf = buf.toString().replace(blogRegex, config.url.replace(/\/$/, ''));
buf = buf.toString().replace(apiRegex, config.apiUrl());
}
content = {
headers: {
Expand Down
32 changes: 22 additions & 10 deletions core/shared/ghost-url.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
(function () {
'use strict';

var apiUrl = '{{api-url}}',
clientId,
clientSecret,
url,
init;

function generateQueryString(object) {
var queries = [],
i;
Expand All @@ -21,12 +27,9 @@
return '';
}

var url = {
config: {},

url = {
api: function () {
var args = Array.prototype.slice.call(arguments),
url = (this.config.useOrigin) ? this.config.origin + this.config.url : this.config.url,
queryOptions;

if (args.length && typeof args[args.length - 1] === 'object') {
Expand All @@ -35,26 +38,35 @@
queryOptions = {};
}

queryOptions.client_id = this.config.clientId;
queryOptions.client_secret = this.config.clientSecret;
queryOptions.client_id = clientId;
queryOptions.client_secret = clientSecret;

if (args.length) {
args.forEach(function (el) {
url += el.replace(/^\/|\/$/g, '') + '/';
apiUrl += el.replace(/^\/|\/$/g, '') + '/';
});
}

return url + generateQueryString(queryOptions);
return apiUrl + generateQueryString(queryOptions);
}
};

init = function (options) {
clientId = options.clientId ? options.clientId : '';
clientSecret = options.clientSecret ? options.clientSecret : '';
apiUrl = options.url ? options.url : '';
};

if (typeof window !== 'undefined') {
window.ghost = window.ghost || {};
url.config = window.ghost.config || {};
window.ghost.url = url;
window.ghost.init = init;
}

if (typeof module !== 'undefined') {
module.exports = url;
module.exports = {
url: url,
init: init
};
}
})();
73 changes: 69 additions & 4 deletions core/test/unit/config_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ describe('Config', function () {

describe('urlPathForPost', function () {
it('should output correct url for post', function () {
config.set({theme: {permalinks: '/:slug/'}});
configUtils.set({theme: {permalinks: '/:slug/'}});

var testData = testUtils.DataGenerator.Content.posts[2],
postLink = '/short-and-sweet/';
Expand All @@ -381,7 +381,7 @@ describe('Config', function () {
});

it('should output correct url for post with date permalink', function () {
config.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});
var testData = testUtils.DataGenerator.Content.posts[2],
today = testData.published_at,
dd = ('0' + today.getDate()).slice(-2),
Expand All @@ -393,7 +393,7 @@ describe('Config', function () {
});

it('should output correct url for page with date permalink', function () {
config.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});
configUtils.set({theme: {permalinks: '/:year/:month/:day/:slug/'}});

var testData = testUtils.DataGenerator.Content.posts[5],
postLink = '/static-page-test/';
Expand All @@ -402,7 +402,7 @@ describe('Config', function () {
});

it('should output correct url for post with complex permalink', function () {
config.set({theme: {permalinks: '/:year/:id/:author/'}});
configUtils.set({theme: {permalinks: '/:year/:id/:author/'}});

var testData = _.extend(
{}, testUtils.DataGenerator.Content.posts[2], {id: 3}, {author: {slug: 'joe-bloggs'}}
Expand All @@ -414,6 +414,71 @@ describe('Config', function () {
config.urlPathForPost(testData).should.equal(postLink);
});
});

describe('apiUrl', function () {
it('should return https config.url if forceAdminSSL set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
forceAdminSSL: true
});

config.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/');
});

it('should return https config.urlSSL if forceAdminSSL set and urlSSL is misconfigured', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'http://other-ghost-blog.com',
forceAdminSSL: true
});

config.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});

it('should return https config.urlSSL if forceAdminSSL set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'https://other-ghost-blog.com',
forceAdminSSL: true
});

config.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});

it('should return https config.urlSSL if set and misconfigured & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'http://other-ghost-blog.com'
});

config.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});

it('should return https config.urlSSL if set & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com',
urlSSL: 'https://other-ghost-blog.com'
});

config.apiUrl().should.eql('https://other-ghost-blog.com/ghost/api/v0.1/');
});

it('should return https config.url if config.url is https & forceAdminSSL is NOT set', function () {
configUtils.set({
url: 'https://my-ghost-blog.com'
});

config.apiUrl().should.eql('https://my-ghost-blog.com/ghost/api/v0.1/');
});

it('should return no protocol config.url if config.url is NOT https & forceAdminSSL/urlSSL is NOT set', function () {
configUtils.set({
url: 'http://my-ghost-blog.com'
});

config.apiUrl().should.eql('//my-ghost-blog.com/ghost/api/v0.1/');
});
});
});

describe('File', function () {
Expand Down
Loading

0 comments on commit 9eadeb9

Please sign in to comment.