mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-14 21:28:11 +02:00
Better error handling for common osm api error conditions
* if 509 Bandwidth Exceeded / 429 Too Many Requests, prompt for login (closes #2262) * if 400 Bad Request / 401 Unauthorized / 403 Forbidden - logout and retry (closes #3546)
This commit is contained in:
+6
-1
@@ -2601,12 +2601,17 @@ img.tile-removing {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
padding: 0px 10px;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.api-status.offline,
|
||||
.api-status.readonly,
|
||||
.api-status.error {
|
||||
background: red;
|
||||
background: #a22;
|
||||
}
|
||||
|
||||
.api-status-login {
|
||||
color: #aaf;
|
||||
}
|
||||
|
||||
/* Modals
|
||||
|
||||
@@ -180,6 +180,7 @@ en:
|
||||
localized_translation_language: Choose language
|
||||
localized_translation_name: Name
|
||||
zoom_in_edit: Zoom in to Edit
|
||||
login: login
|
||||
logout: logout
|
||||
loading_auth: "Connecting to OpenStreetMap..."
|
||||
report_a_bug: Report a bug
|
||||
@@ -191,6 +192,7 @@ en:
|
||||
error: Unable to connect to API.
|
||||
offline: The API is offline. Please try editing later.
|
||||
readonly: The API is read-only. You will need to wait to save your changes.
|
||||
rateLimit: The API is limiting anonymous connections. You can fix this by logging in.
|
||||
commit:
|
||||
title: Save Changes
|
||||
description_placeholder: Brief description of your contributions (required)
|
||||
|
||||
Vendored
+3
-1
@@ -227,6 +227,7 @@
|
||||
"localized_translation_name": "Name"
|
||||
},
|
||||
"zoom_in_edit": "Zoom in to Edit",
|
||||
"login": "login",
|
||||
"logout": "logout",
|
||||
"loading_auth": "Connecting to OpenStreetMap...",
|
||||
"report_a_bug": "Report a bug",
|
||||
@@ -238,7 +239,8 @@
|
||||
"status": {
|
||||
"error": "Unable to connect to API.",
|
||||
"offline": "The API is offline. Please try editing later.",
|
||||
"readonly": "The API is read-only. You will need to wait to save your changes."
|
||||
"readonly": "The API is read-only. You will need to wait to save your changes.",
|
||||
"rateLimit": "The API is limiting anonymous connections. You can fix this by logging in."
|
||||
},
|
||||
"commit": {
|
||||
"title": "Save Changes",
|
||||
|
||||
@@ -64,6 +64,8 @@ export function rendererMap(context) {
|
||||
|
||||
context
|
||||
.on('change.map', immediateRedraw);
|
||||
context.connection()
|
||||
.on('change.map', immediateRedraw);
|
||||
context.history()
|
||||
.on('change.map', immediateRedraw);
|
||||
context.background()
|
||||
|
||||
+60
-23
@@ -9,7 +9,7 @@ import { utilDetect } from '../util/detect';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
|
||||
|
||||
var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading', 'loaded'),
|
||||
var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded'),
|
||||
useHttps = window.location.protocol === 'https:',
|
||||
protocol = useHttps ? 'https:' : 'http:',
|
||||
urlroot = protocol + '//www.openstreetmap.org',
|
||||
@@ -20,25 +20,28 @@ var dispatch = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading',
|
||||
url: urlroot,
|
||||
oauth_consumer_key: '5A043yRSEugj4DJ5TljuapfnrflWDte8jTOcWLlT',
|
||||
oauth_secret: 'aB3jKq1TRsCOUrfOIZ6oQMEDmv2ptV76PA54NGLL',
|
||||
loading: authenticating,
|
||||
done: authenticated
|
||||
loading: authLoading,
|
||||
done: authDone
|
||||
}),
|
||||
rateLimitError,
|
||||
userDetails,
|
||||
off;
|
||||
|
||||
|
||||
function authenticating() {
|
||||
dispatch.call('authenticating');
|
||||
function authLoading() {
|
||||
dispatch.call('authLoading');
|
||||
}
|
||||
|
||||
|
||||
function authenticated() {
|
||||
dispatch.call('authenticated');
|
||||
function authDone() {
|
||||
dispatch.call('authDone');
|
||||
}
|
||||
|
||||
|
||||
function abortRequest(i) {
|
||||
i.abort();
|
||||
if (i) {
|
||||
i.abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,10 +132,10 @@ var parsers = {
|
||||
};
|
||||
|
||||
|
||||
function parse(dom) {
|
||||
if (!dom || !dom.childNodes) return;
|
||||
function parse(xml) {
|
||||
if (!xml || !xml.childNodes) return;
|
||||
|
||||
var root = dom.childNodes[0],
|
||||
var root = xml.childNodes[0],
|
||||
children = root.childNodes,
|
||||
entities = [];
|
||||
|
||||
@@ -151,7 +154,7 @@ function parse(dom) {
|
||||
export default {
|
||||
|
||||
init: function() {
|
||||
this.event = utilRebind(this, dispatch, 'on');
|
||||
utilRebind(this, dispatch, 'on');
|
||||
},
|
||||
|
||||
|
||||
@@ -189,9 +192,34 @@ export default {
|
||||
|
||||
|
||||
loadFromAPI: function(path, callback) {
|
||||
function done(err, dom) {
|
||||
return callback(err, parse(dom));
|
||||
var that = this;
|
||||
|
||||
function done(err, xml) {
|
||||
var isAuthenticated = that.authenticated();
|
||||
|
||||
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
|
||||
// Logout and retry the request..
|
||||
if (isAuthenticated && err &&
|
||||
(err.status === 400 || err.status === 401 || err.status === 403)) {
|
||||
that.logout();
|
||||
that.loadFromAPI(path, callback);
|
||||
|
||||
// else, no retry..
|
||||
} else {
|
||||
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
|
||||
// Set the rateLimitError flag and trigger a warning..
|
||||
if (!isAuthenticated && !rateLimitError && err &&
|
||||
(err.status === 509 || err.status === 429)) {
|
||||
rateLimitError = err;
|
||||
dispatch.call('change');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(err, parse(xml));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.authenticated()) {
|
||||
return oauth.xhr({ method: 'GET', path: path }, done);
|
||||
} else {
|
||||
@@ -402,8 +430,13 @@ export default {
|
||||
|
||||
status: function(callback) {
|
||||
function done(capabilities) {
|
||||
var apiStatus = capabilities.getElementsByTagName('status');
|
||||
callback(undefined, apiStatus[0].getAttribute('api'));
|
||||
if (rateLimitError) {
|
||||
callback(rateLimitError, 'rateLimited');
|
||||
} else {
|
||||
var apiStatus = capabilities.getElementsByTagName('status'),
|
||||
val = apiStatus[0].getAttribute('api');
|
||||
callback(undefined, val);
|
||||
}
|
||||
}
|
||||
d3.xml(urlroot + '/api/capabilities').get()
|
||||
.on('load', done)
|
||||
@@ -467,8 +500,10 @@ export default {
|
||||
inflight[id] = that.loadFromAPI(
|
||||
'/api/0.6/map?bbox=' + tile.extent.toParam(),
|
||||
function(err, parsed) {
|
||||
loadedTiles[id] = true;
|
||||
delete inflight[id];
|
||||
if (!err) {
|
||||
loadedTiles[id] = true;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(err, _.extend({ data: parsed }, tile));
|
||||
@@ -477,7 +512,8 @@ export default {
|
||||
if (_.isEmpty(inflight)) {
|
||||
dispatch.call('loaded');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -487,10 +523,10 @@ export default {
|
||||
|
||||
oauth.options(_.extend({
|
||||
url: urlroot,
|
||||
loading: authenticating,
|
||||
done: authenticated
|
||||
loading: authLoading,
|
||||
done: authDone
|
||||
}, options));
|
||||
dispatch.call('auth');
|
||||
dispatch.call('change');
|
||||
this.reset();
|
||||
return this;
|
||||
},
|
||||
@@ -512,7 +548,7 @@ export default {
|
||||
logout: function() {
|
||||
userDetails = undefined;
|
||||
oauth.logout();
|
||||
dispatch.call('auth');
|
||||
dispatch.call('change');
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -520,7 +556,8 @@ export default {
|
||||
authenticate: function(callback) {
|
||||
userDetails = undefined;
|
||||
function done(err, res) {
|
||||
dispatch.call('auth');
|
||||
rateLimitError = undefined;
|
||||
dispatch.call('change');
|
||||
if (callback) callback(err, res);
|
||||
}
|
||||
return oauth.authenticate(done);
|
||||
|
||||
@@ -67,7 +67,9 @@ export function uiAccount(context) {
|
||||
.attr('id', 'userLink')
|
||||
.classed('hide', true);
|
||||
|
||||
connection.event.on('auth.account', function() { update(selection); });
|
||||
connection
|
||||
.on('change.account', function() { update(selection); });
|
||||
|
||||
update(selection);
|
||||
};
|
||||
}
|
||||
|
||||
+4
-3
@@ -271,14 +271,15 @@ export function uiInit(context) {
|
||||
.call(uiRestore(context));
|
||||
|
||||
var authenticating = uiLoading(context)
|
||||
.message(t('loading_auth'));
|
||||
.message(t('loading_auth'))
|
||||
.blocking(true);
|
||||
|
||||
context.connection()
|
||||
.on('authenticating.ui', function() {
|
||||
.on('authLoading.ui', function() {
|
||||
context.container()
|
||||
.call(authenticating);
|
||||
})
|
||||
.on('authenticated.ui', function() {
|
||||
.on('authDone.ui', function() {
|
||||
authenticating.close();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ export function uiSpinner(context) {
|
||||
.attr('src', context.imagePath('loader-black.gif'))
|
||||
.style('opacity', 0);
|
||||
|
||||
connection.event
|
||||
connection
|
||||
.on('loading.spinner', function() {
|
||||
img.transition()
|
||||
.style('opacity', 1);
|
||||
});
|
||||
|
||||
connection.event
|
||||
connection
|
||||
.on('loaded.spinner', function() {
|
||||
img.transition()
|
||||
.style('opacity', 0);
|
||||
|
||||
+23
-6
@@ -1,18 +1,36 @@
|
||||
import * as d3 from 'd3';
|
||||
import { t } from '../util/locale';
|
||||
import { svgIcon } from '../svg/index';
|
||||
|
||||
|
||||
export function uiStatus(context) {
|
||||
var connection = context.connection(),
|
||||
errCount = 0;
|
||||
var connection = context.connection();
|
||||
|
||||
return function(selection) {
|
||||
|
||||
function update() {
|
||||
connection.status(function(err, apiStatus) {
|
||||
selection.html('');
|
||||
if (err && errCount++ < 2) return;
|
||||
|
||||
if (err) {
|
||||
selection.text(t('status.error'));
|
||||
if (apiStatus === 'rateLimited') {
|
||||
selection
|
||||
.text(t('status.rateLimit'))
|
||||
.append('a')
|
||||
.attr('class', 'api-status-login')
|
||||
.attr('target', '_blank')
|
||||
.call(svgIcon('#icon-out-link', 'inline'))
|
||||
.append('span')
|
||||
.text(t('login'))
|
||||
.on('click.login', function() {
|
||||
d3.event.preventDefault();
|
||||
connection.authenticate();
|
||||
});
|
||||
} else {
|
||||
// TODO: nice messages for different error types
|
||||
selection.text(t('status.error'));
|
||||
}
|
||||
|
||||
} else if (apiStatus === 'readonly') {
|
||||
selection.text(t('status.readonly'));
|
||||
} else if (apiStatus === 'offline') {
|
||||
@@ -20,12 +38,11 @@ export function uiStatus(context) {
|
||||
}
|
||||
|
||||
selection.attr('class', 'api-status ' + (err ? 'error' : apiStatus));
|
||||
if (!err) errCount = 0;
|
||||
});
|
||||
}
|
||||
|
||||
connection
|
||||
.on('auth', function() { update(selection); });
|
||||
.on('change', function() { update(selection); });
|
||||
|
||||
window.setInterval(update, 90000);
|
||||
update(selection);
|
||||
|
||||
@@ -44,9 +44,9 @@ describe('iD.serviceOsm', function () {
|
||||
expect(connection.changesetURL(1)).to.equal('http://example.com/changeset/1');
|
||||
});
|
||||
|
||||
it('emits an auth event', function(done) {
|
||||
connection.on('auth', function() {
|
||||
connection.on('auth', null);
|
||||
it('emits a change event', function(done) {
|
||||
connection.on('change', function() {
|
||||
connection.on('change', null);
|
||||
done();
|
||||
});
|
||||
connection.switch({ urlroot: 'http://example.com' });
|
||||
|
||||
Reference in New Issue
Block a user