Skip to content
This repository was archived by the owner on Jan 6, 2023. It is now read-only.

Commit 84de70c

Browse files
sebastienbarbiervladikoff
authored andcommitted
Handle sync errors (#824)
After authenticating, kinto might return some errors we should handle. fix synced UI after kinto error. Should request reconnect. implement a solution to unblock the user. display used quota. #837 Fix #791 #801 #802 #827
1 parent 5dfd1ff commit 84de70c

File tree

8 files changed

+73
-19
lines changed

8 files changed

+73
-19
lines changed

docs/metrics.md

+5-7
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ An event fired when the user completes a change of the content of the notepad. I
6969
- `cd6`
7070

7171
#### `drag-n-drop`
72-
7372
An event fired when the user tries to drag or drop a content into the notepad.
7473

7574
- `ec` - `notes`
@@ -142,7 +141,6 @@ An event fired when the "Send to Notes" context menu is used
142141
- `ea` - `metrics-context-menu`
143142

144143
#### `limit-reached`
145-
146144
An event fired when user goes over the pad limit (15000 character)
147145

148146
- `ec` - `notes`
@@ -157,15 +155,15 @@ An event fired when user goes over the pad limit (15000 character)
157155
- `cd6`
158156

159157
#### `idb-fail`
160-
161158
An event fired when IndexedDB fails to load
162159

163160
- `ec` - `notes`
164161
- `ea` - `idb-fail`
165162

166-
167-
#### `migrate-single-note`
168-
An event fired when migrating singleNote to multiNote
163+
### `delete-deleted-notes`
164+
A client retrieved notes which have been deleted on client side but not proparly
165+
deleted on server side. Those were deleted before v4.0.0-beta.4 (during multi-note implementation).
169166

170167
- `ec` - `notes`
171-
- `ea` - `migrate-single-note`
168+
- `ea` - `delete-deleted-notes`
169+

locales/en-US/notes.properties

+1
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,4 @@ backToAllNotes=Back to all notes
122122
newNote=New Note
123123
makePlainText=Make Plain Text
124124
deleteNote=Delete Note
125+
insufficientStorage=Insufficient Storage

src/sidebar/app/actions.js

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SYNC_AUTHENTICATED,
1111
PLEASE_LOGIN,
1212
OPENING_LOGIN,
1313
FOCUS_NOTE,
14+
ERROR,
1415
REQUEST_WELCOME_PAGE } from './utils/constants';
1516

1617
import INITIAL_CONTENT from './data/initialContent';
@@ -169,3 +170,7 @@ export function setFocusedNote(id) {
169170
export function requestWelcomeNote() {
170171
return { type: REQUEST_WELCOME_PAGE };
171172
}
173+
174+
export function error(message) {
175+
return { type: ERROR, message};
176+
}

src/sidebar/app/components/Footer.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class Footer extends React.Component {
3939
isReconnectState: true,
4040
text: () => browser.i18n.getMessage('reconnectSync')
4141
},
42+
ERROR: {
43+
yellowBackground: true,
44+
isClickable: false
45+
},
4246
SYNCING: {
4347
animateSyncIcon: true,
4448
text: () => browser.i18n.getMessage('syncProgress')
@@ -52,7 +56,10 @@ class Footer extends React.Component {
5256
this.getFooterState = (state) => {
5357
let res;
5458
if (state.sync.email) { // If user is authenticated
55-
if (state.sync.isSyncing) {
59+
if (state.sync.error) {
60+
res = this.STATES.ERROR;
61+
res.text = () => state.sync.error;
62+
} else if (state.sync.isSyncing) {
5663
res = this.STATES.SYNCING;
5764
} else {
5865
res = this.STATES.SYNCED;

src/sidebar/app/onMessage.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SYNC_AUTHENTICATED,
44
TEXT_SYNCED,
55
RECONNECT_SYNC,
66
DISCONNECTED,
7+
ERROR,
78
SEND_TO_NOTES } from './utils/constants';
89
// Actions
910
import { authenticate,
@@ -12,7 +13,8 @@ import { authenticate,
1213
synced,
1314
reconnectSync,
1415
sendToNote,
15-
kintoLoad } from './actions';
16+
kintoLoad,
17+
error } from './actions';
1618
import store from './store';
1719
/**
1820
* For each event, action on redux to update UI. No longer any event from chrome in components
@@ -56,6 +58,9 @@ chrome.runtime.onMessage.addListener(eventData => {
5658
case RECONNECT_SYNC:
5759
store.dispatch(reconnectSync());
5860
break;
61+
case ERROR:
62+
store.dispatch(error(eventData.message));
63+
break;
5964
case DISCONNECTED:
6065
if (store.getState().sync.email) {
6166
store.dispatch(disconnect());

src/sidebar/app/reducers.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
PLEASE_LOGIN,
1313
FOCUS_NOTE,
1414
SEND_TO_NOTES,
15+
ERROR,
1516
REQUEST_WELCOME_PAGE
1617
} from './utils/constants';
1718

@@ -26,14 +27,16 @@ function sync(sync = {}, action) {
2627
isReconnectSync: false,
2728
email: action.email,
2829
lastSynced: new Date(),
29-
isSyncing: true
30+
isSyncing: true,
31+
error: null
3032
});
3133
case DISCONNECTED:
3234
return Object.assign({}, sync, {
3335
email: null,
3436
isOpeningLogin: false,
3537
isPleaseLogin: false,
3638
isReconnectSync: false,
39+
error: null
3740
});
3841
case OPENING_LOGIN:
3942
return Object.assign({}, sync, {
@@ -46,20 +49,25 @@ function sync(sync = {}, action) {
4649
isOpeningLogin: false,
4750
isPleaseLogin: true,
4851
isReconnectSync: false,
52+
error: null
4953
});
5054
case RECONNECT_SYNC:
5155
return Object.assign({}, sync, {
56+
email: null,
5257
isOpeningLogin: false,
5358
isPleaseLogin: false,
5459
isReconnectSync: true,
60+
error: null
5561
});
5662
case DELETE_NOTE:
5763
return Object.assign({}, sync, {
58-
isSyncing: true
64+
isSyncing: true,
65+
error: null
5966
});
6067
case UPDATE_NOTE:
6168
return Object.assign({}, sync, {
62-
isSyncing: true
69+
isSyncing: true,
70+
error: null
6371
});
6472
case TEXT_SYNCED:
6573
return Object.assign({}, sync, {
@@ -84,6 +92,10 @@ function sync(sync = {}, action) {
8492
return Object.assign({}, sync, {
8593
welcomePage: false
8694
});
95+
case ERROR:
96+
return Object.assign({}, sync, {
97+
error: action.message
98+
});
8799
default:
88100
return sync;
89101
}

src/sidebar/app/utils/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ export const UPDATE_NOTE = 'text-change';
1919
export const DELETE_NOTE = 'delete-note';
2020

2121
export const FOCUS_NOTE = 'focus-note';
22+
export const ERROR = 'error';
2223
export const REQUEST_WELCOME_PAGE = 'request-welcome-page';

src/sync.js

+32-7
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ function decrypt(key, encrypted) {
2828

2929
// An "id schema" used to validate Kinto IDs and generate new ones.
3030
const notesIdSchema = { // eslint-disable-line no-unused-vars
31-
// FIXME: Maybe this should generate IDs?
31+
// We do not generate ID to keep retrocompatibility with single note version.
3232
generate() {
3333
throw new Error('cannot generate IDs');
3434
},
3535

3636
validate() {
37-
// FIXME: verify that at least this matches Kinto server ID format
3837
return true;
3938
},
4039
};
@@ -51,6 +50,8 @@ class ServerKeyOlderError extends Error {
5150
}
5251
}
5352

53+
const deletedNotesStillOnServer = {};
54+
5455
class JWETransformer {
5556
constructor(key) {
5657
this.key = key;
@@ -61,13 +62,10 @@ class JWETransformer {
6162
const ciphertext = await encrypt(this.key, record);
6263
// Copy over the _status field, so that we handle concurrency
6364
// headers (If-Match, If-None-Match) correctly.
64-
// DON'T copy over "deleted" status, because then we'd leak
65-
// plaintext deletes.
66-
const status = record._status && (record._status === 'deleted' ? 'updated' : record._status);
6765
const encryptedResult = {
6866
content: ciphertext,
6967
id: record.id,
70-
_status: status,
68+
_status: record._status,
7169
kid: this.key.kid,
7270
};
7371
if (record.hasOwnProperty('last_modified')) {
@@ -104,6 +102,12 @@ class JWETransformer {
104102
// If we get such a record, flag it as deleted.
105103
if (decoded._status === 'deleted') {
106104
decoded.deleted = true;
105+
// On decode, we flag notes with _status deleted but still on server.
106+
// We automatically will request deletion for those.
107+
// (This is due to singleNote replacing 'deleted' state by 'updated')
108+
// Should be deleted when every user who tried beta runned it once.
109+
// (see metrics deleteDeleted)
110+
deletedNotesStillOnServer[decoded.id] = decoded;
107111
}
108112
return decoded;
109113
}
@@ -261,6 +265,16 @@ function syncKinto(client, credentials) {
261265
} else if (error.message === 'Failed to renew token') {
262266
// cannot refresh the access token, log the user out.
263267
return reconnectSync(credentials);
268+
} else if (error.response
269+
&& error.response.status === 507
270+
&& error.message.includes('Insufficient Storage')) {
271+
272+
// cannot refresh the access token, log the user out.
273+
browser.runtime.sendMessage('notes@mozilla.com', {
274+
action: 'error',
275+
message: browser.i18n.getMessage('insufficientStorage')
276+
});
277+
return Promise.reject(error);
264278
}
265279
console.error(error); // eslint-disable-line no-console
266280
reconnectSync(credentials);
@@ -276,7 +290,18 @@ function reconnectSync(credentials) {
276290
}
277291

278292
function retrieveNote(client) {
279-
return client.collection('notes', { idSchema: notesIdSchema }).list({});
293+
return client
294+
.collection('notes', { idSchema: notesIdSchema })
295+
.list({})
296+
.then((list) => {
297+
// We delete all notes retrieved from server and not properly deleted
298+
Object.keys(deletedNotesStillOnServer).forEach((id) => {
299+
sendMetrics('delete-deleted-notes'); // eslint-disable-line no-undef
300+
client.collection('notes', { idSchema: notesIdSchema }).deleteAny(id);
301+
});
302+
303+
return list;
304+
});
280305
}
281306

282307
/**

0 commit comments

Comments
 (0)