diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 13c803075..f07c5c7e8 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -5,7 +5,8 @@ define([ '/customize/messages.js', 'jquery', '/api/config', -], function (h, Language, AppConfig, Msg, $, ApiConfig) { + 'optional!/api/instance', +], function (h, Language, AppConfig, Msg, $, ApiConfig, Instance) { var Pages = {}; Pages.setHTML = function (e, html) { @@ -138,6 +139,21 @@ define([ }); var value = AppConfig.hostDescription; Pages.hostDescription = (value && (value[l] || value.default)) || Msg.home_host; + + Pages.Instance = {}; + Object.keys(Instance).forEach(function (k) { + var value = Instance[k]; + var result = Pages.Instance[k] = value[l] || value.default || undefined; + }); + + var name; + try { + name = Pages.Instance.name || new URL('/', ApiConfig.httpUnsafeOrigin).host; + } catch (err) { + name = 'CryptPad'; + } + Pages.Instance.name = name; + Pages.Instance.description = Pages.Instance.description || Msg.main_catch_phrase; }()); // used for the about menu diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index eb32eb2d7..bd1b9a5e9 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -11,7 +11,8 @@ define([ '/customize/messages.js', '/customize/application_config.js', '/common/outer/local-store.js', - '/customize/pages.js' + '/customize/pages.js', + //'json!/api/instance', ], function ($, Config, h, Feedback, UI, Hash, Constants, Util, TextFit, Msg, AppConfig, LocalStore, Pages) { var urlArgs = Config.requireConf.urlArgs; @@ -177,6 +178,19 @@ define([ ])); } + // instance location + var locationBlock; + + if (Pages.Instance.location) { + locationBlock = h('div.cp-instance-location', [ + h('i.fa.fa-map-pin', {'aria-hidden': 'true'}), + 'Encrypted data is hosted in: ', // XXX translate + Pages.Instance.location, + ]); + } else { + locationBlock = h('div', h('br')); // XXX + } + return [ h('div#cp-main', [ Pages.infopageTopbar(), @@ -188,12 +202,9 @@ define([ 'aria-hidden': 'true', alt: '' }), - h('h1', 'CryptPad.fr'), // XXX use instance name - UI.setHTML(h('span.tag-line'), Msg.main_catch_phrase), // XXX Use instance description - h('div.cp-instance-location', [ - h('i.fa.fa-map-pin', {'aria-hidden': 'true'}), - 'Encrypted data is hosted in: France (OVH)' // XXX Use instance location - ]), + h('h1', Pages.Instance.name), + UI.setHTML(h('span.tag-line'), Pages.Instance.description), + locationBlock, termsLink, privacyLink, imprintLink diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index a2d1cb1ce..23139c58c 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -183,7 +183,7 @@ server { # /api/config is loaded once per page load and is used to retrieve # the caching variable which is applied to every other resource # which is loaded during that session. - location ~ ^/api/(config|broadcast).*$ { + location ~ ^/api/.*$ { proxy_pass http://localhost:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; diff --git a/server.js b/server.js index 7c9667d3c..cd48835d3 100644 --- a/server.js +++ b/server.js @@ -261,6 +261,27 @@ var serveBroadcast = makeRouteCache(function (host) { app.get('/api/config', serveConfig); app.get('/api/broadcast', serveBroadcast); +var define = function (obj) { + return `define(function (){ + return ${JSON.stringify(obj, null, '\t')}; +});` +}; + +app.get('/api/instance', function (req, res) { // XXX use caching? + res.setHeader('Content-Type', 'text/javascript'); + res.send(define({ + name: { + default: Env.instanceName, + }, + description: { + default: Env.instanceDescription, + }, + location: { + default: Env.instanceJurisdiction, + }, + })); +}); + var four04_path = Path.resolve(__dirname + '/customize.dist/404.html'); var fivehundred_path = Path.resolve(__dirname + '/customize.dist/500.html'); var custom_four04_path = Path.resolve(__dirname + '/customize/404.html'); diff --git a/www/checkup/dependency-warning.js b/www/checkup/dependency-warning.js index f55a83b6a..fd07c939b 100644 --- a/www/checkup/dependency-warning.js +++ b/www/checkup/dependency-warning.js @@ -12,6 +12,7 @@ var first = true; window.addEventListener('error', function (ev) { + if (window.CHECKUP_MAIN_LOADED) { return; } if (!ev) { return; } var srcElement = ev.srcElement; if (!srcElement) { return; } diff --git a/www/checkup/main.js b/www/checkup/main.js index 01192cfce..a71e2c3f0 100644 --- a/www/checkup/main.js +++ b/www/checkup/main.js @@ -22,6 +22,8 @@ define([ ], function ($, ApiConfig, Assertions, h, Messages, DomReady, nThen, SFCommonO, Login, Hash, Util, Pinpad, NetConfig, Pages, Tools, AppConfig) { + window.CHECKUP_MAIN_LOADED = true; + var Assert = Assertions(); var trimSlashes = function (s) { if (typeof(s) !== 'string') { return s; } @@ -1364,6 +1366,25 @@ define([ }); }); + assert(function (cb, msg) { + var url = '/api/instance'; + msg.appendChild(h('span', [ + link(url, url), + " did not load as expected. This is most likely caused by a missing directive in your reverse proxy or an outdated version of the API server.", + ])); + + require([ + `optional!${url}`, + ], function (Instance) { + // if the URL fails to load then an empty object will be returned + // this can be interpreted as a failure, even though the rest of the platform should still work + if (!Object.keys(Instance).length) { + return void cb(Instance); + } + cb(true); + }); + }); + var serverToken; Tools.common_xhr('/', function (xhr) { serverToken = xhr.getResponseHeader('server'); diff --git a/www/common/requireconfig.js b/www/common/requireconfig.js index 9068b2623..f94936df7 100644 --- a/www/common/requireconfig.js +++ b/www/common/requireconfig.js @@ -8,6 +8,7 @@ define([ // json plugin text: '/bower_components/requirejs-plugins/lib/text', json: '/bower_components/requirejs-plugins/src/json', + optional: '/lib/optional/optional', // jquery declares itself as literally "jquery" so it cannot be pulled by path :( "jquery": "/bower_components/jquery/dist/jquery.min", "mermaid": "/lib/mermaid/mermaid.min", diff --git a/www/lib/changelog.md b/www/lib/changelog.md index 433a8e104..2ae4af7a2 100644 --- a/www/lib/changelog.md +++ b/www/lib/changelog.md @@ -10,4 +10,5 @@ This file is intended to be used as a log of what third-party source we have ven * [pdfjs](https://mozilla.github.io/pdf.js/) with some minor modifications to prevent CSP errors * [mermaid 9.0.0](https://github.com/mermaid-js/mermaid/releases/tag/8.13.4) extends our markdown integration to support a variety of diagram types * [Fabricjs 4.6.0](https://github.com/fabricjs/fabric.js) and [Fabric-history](https://github.com/lyzerk/fabric-history) for the whiteboard app +* [Requirejs optional module plugin](https://stackoverflow.com/a/27422370) diff --git a/www/lib/optional/optional.js b/www/lib/optional/optional.js new file mode 100644 index 000000000..4c4c0b8ca --- /dev/null +++ b/www/lib/optional/optional.js @@ -0,0 +1,33 @@ +define("optional", [], { + load : function (moduleName, parentRequire, onload, config){ + + var onLoadSuccess = function(moduleInstance){ + // Module successfully loaded, call the onload callback so that + // requirejs can work its internal magic. + onload(moduleInstance); + } + + var onLoadFailure = function(err){ + // optional module failed to load. + var failedId = err.requireModules && err.requireModules[0]; + console.warn("Could not load optional module: " + failedId); + + // Undefine the module to cleanup internal stuff in requireJS + requirejs.undef(failedId); + + // Now define the module instance as a simple empty object + // (NOTE: you can return any other value you want here) + define(failedId, [], function(){return {};}); + + // Now require the module make sure that requireJS thinks + // that is it loaded. Since we've just defined it, requirejs + // will not attempt to download any more script files and + // will just call the onLoadSuccess handler immediately + parentRequire([failedId], onLoadSuccess); + } + + parentRequire([moduleName], onLoadSuccess, onLoadFailure, { + accept: 'application/json', + }); + } +});