diff --git a/core/server/api/canary/oembed.js b/core/server/api/canary/oembed.js index 5bdaabe75f..0b47646cd7 100644 --- a/core/server/api/canary/oembed.js +++ b/core/server/api/canary/oembed.js @@ -45,7 +45,11 @@ async function fetchBookmarkData(url, html) { metadata }); } - return Promise.resolve(); + + return Promise.reject(new errors.ValidationError({ + message: i18n.t('errors.api.oembed.insufficientMetadata'), + context: url + })); } const findUrlWithProvider = (url) => { @@ -197,6 +201,18 @@ function fetchOembedData(_url) { }); } +function errorHandler(url) { + return function (err) { + // allow specific validation errors through for better error messages + if (errors.utils.isIgnitionError(err) && err.errorType === 'ValidationError') { + return Promise.reject(err); + } + + // default to unknown provider to avoid leaking any app specifics + return unknownProvider(url); + }; +} + module.exports = { docName: 'oembed', @@ -212,7 +228,7 @@ module.exports = { if (type === 'bookmark') { return fetchBookmarkData(url) - .catch(() => unknownProvider(url)); + .catch(errorHandler(url)); } return fetchOembedData(url).then((response) => { @@ -225,9 +241,7 @@ module.exports = { return unknownProvider(url); } return response; - }).catch(() => { - return unknownProvider(url); - }); + }).catch(errorHandler(url)); } } }; diff --git a/core/server/translations/en.json b/core/server/translations/en.json index a52b142eb3..493bf6c424 100644 --- a/core/server/translations/en.json +++ b/core/server/translations/en.json @@ -438,6 +438,7 @@ }, "oembed": { "noUrlProvided": "No url provided.", + "insufficientMetadata": "URL contains insufficient metadata for bookmark card.", "unknownProvider": "No provider found for supplied URL." }, "userMessages": { diff --git a/test/api-acceptance/admin/oembed_spec.js b/test/api-acceptance/admin/oembed_spec.js index 2201bcdc1c..f31d6131d0 100644 --- a/test/api-acceptance/admin/oembed_spec.js +++ b/test/api-acceptance/admin/oembed_spec.js @@ -66,6 +66,89 @@ describe('Oembed API', function () { }); }); + describe('type: bookmark', function () { + it('can fetch a bookmark with ?type=bookmark', function (done) { + const pageMock = nock('https://example.com') + .get('/') + .reply( + 200, + 'TESTING', + {'content-type': 'text/html'} + ); + + const url = encodeURIComponent('https://example.com'); + request.get(localUtils.API.getApiQuery(`oembed/?url=${url}&type=bookmark`)) + .set('Origin', config.get('url')) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + pageMock.isDone().should.be.true(); + res.body.type.should.eql('bookmark'); + res.body.url.should.eql('https://example.com'); + res.body.metadata.title.should.eql('TESTING'); + done(); + }); + }); + + it('falls back to bookmark without ?type=embed and no oembed metatag', function (done) { + const pageMock = nock('https://example.com') + .get('/') + .times(2) // 1st = oembed metatag check, 2nd = metascraper + .reply( + 200, + 'TESTING', + {'content-type': 'text/html'} + ); + + const url = encodeURIComponent('https://example.com'); + request.get(localUtils.API.getApiQuery(`oembed/?url=${url}`)) + .set('Origin', config.get('url')) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200) + .end(function (err, res) { + if (err) { + return done(err); + } + pageMock.isDone().should.be.true(); + res.body.type.should.eql('bookmark'); + res.body.url.should.eql('https://example.com'); + res.body.metadata.title.should.eql('TESTING'); + done(); + }); + }); + + it('errors with useful message when title is unavailable', function (done) { + const pageMock = nock('https://example.com') + .get('/') + .reply( + 200, + '', + {'content-type': 'text/html'} + ); + + const url = encodeURIComponent('https://example.com'); + request.get(localUtils.API.getApiQuery(`oembed/?type=bookmark&url=${url}`)) + .set('Origin', config.get('url')) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(422) + .end(function (err, res) { + if (err) { + return done(err); + } + pageMock.isDone().should.be.true(); + should.exist(res.body.errors); + res.body.errors[0].context.should.match(/insufficient metadata/i); + done(); + }); + }); + }); + describe('with unknown provider', function () { it('fetches url and follows redirects', function (done) { const redirectMock = nock('http://test.com/')