diff --git a/src/app/contacts-app/contacts-settings.component.html b/src/app/contacts-app/contacts-settings.component.html
index f1882acd6..d605b6d0e 100644
--- a/src/app/contacts-app/contacts-settings.component.html
+++ b/src/app/contacts-app/contacts-settings.component.html
@@ -40,7 +40,8 @@
Avatars (contact pictures)
(change)="saveAvatarSource()"
>
- Load from external services (like gravatar.com)
+ Load from external services (like gravatar.com
+ and libravatar.org)
Only show pictures stored locally
diff --git a/src/app/contacts-app/contacts.service.spec.ts b/src/app/contacts-app/contacts.service.spec.ts
index 409cc88e6..01d6e7d6d 100644
--- a/src/app/contacts-app/contacts.service.spec.ts
+++ b/src/app/contacts-app/contacts.service.spec.ts
@@ -59,15 +59,22 @@ describe('ContactsService', () => {
let storage: StorageService;
const prefService = new MockPrefService() as unknown as PreferencesService;
let sut: ContactsService;
+ let fetchSpy: jasmine.Spy;
beforeEach(async () => {
+ fetchSpy = spyOn(window, 'fetch').and.callFake((url: RequestInfo): Promise => {
+ const avatarUrl = url.toString();
+ const responseOk = (avatarUrl.includes('gravatar.com') && avatarUrl.includes('d770e753373e7b886680847fd773396d'))
+ || (avatarUrl.includes('libravatar.org') && avatarUrl.includes('291167d1fdc699e76ee26f977b87a906'));
+ return Promise.resolve({ ok: responseOk } as Response);
+ });
rmmapi = new MockRMMAPI();
storage = new StorageService(rmmapi as unknown as RunboxWebmailAPI);
sut = new ContactsService(rmmapi as unknown as RunboxWebmailAPI, prefService, storage);
});
describe('Avatar lookup', () => {
- it('should look up remote avatars (gravatar)', (done) => {
+ it('should look up remote avatars', (done) => {
prefService.set(prefService.prefGroup, 'avatarSource', 'remote' );
prefService.preferences.pipe(take(1)).subscribe(async _ => {
@@ -75,12 +82,22 @@ describe('ContactsService', () => {
let avatarUrl = await sut.lookupAvatar('test+gravatar@runbox.com');
expect(avatarUrl).toMatch(/gravatar/);
+ avatarUrl = await sut.lookupAvatar('test+libravatar@runbox.com');
+ expect(avatarUrl).toMatch(/libravatar/);
+
// local avatar wins over gravatar
avatarUrl = await sut.lookupAvatar('test@runbox.com');
expect(avatarUrl).toMatch(/test.url/);
- avatarUrl = await sut.lookupAvatar('test+no+gravatar@runbox.com');
+ avatarUrl = await sut.lookupAvatar('test+no+avatar@runbox.com');
expect(avatarUrl).toBeFalsy();
+ expect(fetchSpy.calls.allArgs().map(args => args[0].toString())).toEqual([
+ jasmine.stringMatching(/gravatar\.com/),
+ jasmine.stringMatching(/gravatar\.com/),
+ jasmine.stringMatching(/libravatar\.org/),
+ jasmine.stringMatching(/gravatar\.com/),
+ jasmine.stringMatching(/libravatar\.org/),
+ ]);
done();
});
diff --git a/src/app/contacts-app/contacts.service.ts b/src/app/contacts-app/contacts.service.ts
index 01c3001cc..cd6a8e5c4 100644
--- a/src/app/contacts-app/contacts.service.ts
+++ b/src/app/contacts-app/contacts.service.ts
@@ -45,7 +45,7 @@ interface AvatarCacheEntry {
/* This caches avatar URLs, or `null`s in their absence.
* Mostly useful for avatars not available in Contacts,
- * and loaded from external services like gravatar.
+ * and loaded from external services like Gravatar and Libravatar.
*
* Putting (gr)avatar URLs in
will cache them nicely,
* except if they 404d last time around
@@ -349,14 +349,26 @@ export class ContactsService implements OnDestroy {
return Promise.resolve(null);
}
+ const resolvedUrl = await this.lookupRemoteAvatar(email);
+ this.avatarCache.add(email, resolvedUrl);
+ return resolvedUrl;
+ }
+
+ private async lookupRemoteAvatar(email: string): Promise {
const hash = Md5.hashStr(email.toLowerCase());
- const url = 'https://gravatar.com/avatar/' + hash + '?d=404';
+ const urls = [
+ 'https://gravatar.com/avatar/' + hash + '?d=404',
+ 'https://seccdn.libravatar.org/avatar/' + hash + '?d=404',
+ ];
+
+ for (const url of urls) {
+ const response = await fetch(url);
+ if (response.ok) {
+ return url;
+ }
+ }
- return fetch(url).then(response => {
- const resolvedUrl = response.ok ? url : null;
- this.avatarCache.add(email, resolvedUrl);
- return Promise.resolve(resolvedUrl);
- });
+ return null;
}
lookupContact(email: string): Promise {