Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
Speedup handling of identical avatars
Browse files Browse the repository at this point in the history
  • Loading branch information
the-djmaze committed Oct 28, 2024
1 parent f12de9a commit 490d808
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 58 deletions.
98 changes: 52 additions & 46 deletions plugins/avatars/avatars.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,30 @@
avatars = new Map,
ncAvatars = new Map,
templateId = 'MailMessageView',
b64 = data => btoa(unescape(encodeURIComponent(data))),
b64url = data => b64(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''),
getBimiSelector = msg => {
// Get 's' value out of 'v=BIMI1; s=foo;'
let bimiSelector = msg.headers().valueByName('BIMI-Selector');
bimiSelector = bimiSelector ? bimiSelector.match(/;.*s=([^\s;]+)/)[1] : '';
return bimiSelector || '';
},
getBimiId = msg => ('pass' == msg.from[0].dkimStatus ? 1 : 0) + '-' + getBimiSelector(msg),
getAvatarUrl = msg => `?Avatar/${getBimiId(msg)}/${msg.avatar}`,
getAvatarUid = msg => `${getBimiId(msg)}/${msg.from[0].email.toLowerCase()}`,
getAvatar = msg => ncAvatars.get(msg.from[0].email.toLowerCase()) || avatars.get(getAvatarUid(msg)),
email = msg => msg.from[0].email.toLowerCase(),
getAvatarUid = msg => `${getBimiId(msg)}/${email(msg)}`,
getAvatar = msg => ncAvatars.get(email(msg)) || avatars.get(getAvatarUid(msg)),
getAvatarUri = msg => {
if ('remote' === msg.avatar) {
msg.avatar = `?Avatar/${getBimiId(msg)}/${b64url(email(msg))}`;
}
/*
else if (!msg.avatar.startsWith('data:')) {
msg.avatar = null;
}
*/
return msg.avatar;
},

hash = async txt => {
if (/^[0-9a-f]{15,}$/i.test(txt)) {
return txt;
Expand All @@ -64,58 +78,47 @@
(from.name?.split(/[^\p{L}]+/gu) || []).reduce((a, s) => a + (s[0] || ''), '')
.slice(0,2)
.toUpperCase(),
setIdenticon = (from, fn) => hash(from.email).then(hash =>
fn('data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(window.identiconSvg(
setIdenticon = (msg, fn) => hash(msg.from[0].email).then(hash => {
const uri = 'data:image/svg+xml;base64,' + b64(window.identiconSvg(
hash,
fromChars(from),
fromChars(msg.from[0]),
window.getComputedStyle(getEl('rl-app'), null).getPropertyValue('font-family')
)))))
),
));
// avatars.set(getAvatarUid(msg), uri);
fn(uri);
}),

addQueue = (msg, fn) => {
msg.from?.[0] && setIdenticon(msg.from[0], fn);
if (rl.pluginSettingsGet('avatars', 'delay')) {
queue.push([msg, fn]);
runQueue();
} else if (msg.avatar) {
fn(getAvatarUrl(msg));
if (msg.from?.[0]) {
if (getAvatarUri(msg)) {
if (rl.pluginSettingsGet('avatars', 'delay')) {
setIdenticon(msg, fn);
queue.push([msg, fn]);
runQueue();
} else {
fn(msg.avatar);
}
} else {
setIdenticon(msg, fn);
}
}
},
runQueue = (() => {
let item = queue.shift();
while (item) {
if (item[0].from) {
let url = getAvatar(item[0]),
uid = getAvatarUid(item[0]);
if (url) {
item[1](url);
item = queue.shift();
continue;
} else if (item[0].avatar) {
item[1](getAvatarUrl(item[0]));
} else if (!avatars.has(uid)) {
let from = item[0].from[0];
rl.pluginRemoteRequest((iError, data) => {
if (!iError && data?.Result.type) {
url = `data:${data.Result.type};base64,${data.Result.data}`;
avatars.set(uid, url);
item[1](url);
} else {
avatars.set(uid, '');
}
runQueue();
}, 'Avatar', {
bimi: 'pass' == from.dkimStatus ? 1 : 0,
bimiSelector: getBimiSelector(item[0]),
email: from.email
});
}
let url = getAvatar(item[0]);
if (url) {
item[1](url);
item = queue.shift();
continue;
} else if (item[0].avatar) {
item[1](item[0].avatar);
}
runQueue();
break;
}
}).debounce(1000);

// addEventListener('DOMContentLoaded', () => {
/**
* Modify templates
*/
Expand Down Expand Up @@ -188,13 +191,19 @@
fn = url=>{element.src = url};
element.onerror = ()=>{
element.onerror = null;
setIdenticon(msg.from[0], fn);
setIdenticon(msg, fn);
};
if (url) {
fn(url);
} else if (msg.avatar?.startsWith('data:')) {
fn(msg.avatar);
} else {
element.onload = ()=>{
if (!element.src.startsWith('data:')) {
element.onload = null;
avatars.set(getAvatarUid(msg), element.src);
}
};
addQueue(msg, fn);
}
}
Expand All @@ -221,11 +230,8 @@
if (url) {
fn(url);
} else if (msg.avatar) {
fn(msg.avatar.startsWith('data:') ? msg.avatar : getAvatarUrl(msg));
fn(getAvatarUri(msg));
} else {
// let from = msg.from[0];
// view.viewUserPic(`?Avatar/${'pass' == from.dkimStatus ? 1 : 0}/${encodeURIComponent(from.email)}`);
// view.viewUserPicVisible(true);
addQueue(msg, fn);
}
}
Expand Down
22 changes: 10 additions & 12 deletions plugins/avatars/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
NAME = 'Avatars',
AUTHOR = 'SnappyMail',
URL = 'https://snappymail.eu/',
VERSION = '1.20',
RELEASE = '2024-08-26',
VERSION = '1.21',
RELEASE = '2024-10-28',
REQUIRED = '2.33.0',
CATEGORY = 'Contacts',
LICENSE = 'MIT',
Expand Down Expand Up @@ -79,11 +79,9 @@ private function JsonAvatar($message) : ?string
|| ($this->Config()->Get('plugin', 'bimi', false) && 'pass' == $mFrom['dkimStatus'])
|| ($this->Config()->Get('plugin', 'favicon', false) && 'pass' == $mFrom['dkimStatus'])
)
) try {
// Base64Url
return \SnappyMail\Crypt::EncryptUrlSafe($mFrom['email']);
} catch (\Throwable $e) {
\SnappyMail\Log::error('Crypt', $e->getMessage());
) {
// return \MailSo\Base\Utils::UrlSafeBase64Encode(\mb_strtolower($mFrom['email']));
return 'remote';
}
if ('pass' == $mFrom['dkimStatus'] && $this->Config()->Get('plugin', 'service', true)) {
// 'data:image/png;base64,[a-zA-Z0-9+/=]'
Expand Down Expand Up @@ -112,17 +110,17 @@ public function DoAvatar() : array
}

/**
* GET /?Avatar/${bimi}/Encrypted(${from.email})
* Nextcloud Mail uses insecure unencrypted 'index.php/apps/mail/api/avatars/url/local%40example.com'
* GET /?Avatar/${bimi}/Encoded(${from.email})
* Nextcloud Mail uses insecure unencoded 'index.php/apps/mail/api/avatars/url/local%40example.com'
*/
// public function ServiceAvatar(...$aParts)
public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEncryptedEmail)
public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEncodedEmail)
{
$maxAge = 86400;
$sEmail = \SnappyMail\Crypt::DecryptUrlSafe($sEncryptedEmail);
$sEmail = \MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedEmail);
$aBimi = \explode('-', $sBimi, 2);
$sBimiSelector = isset($aBimi[1]) ? $aBimi[1] : 'default';
// $sEmail && \MailSo\Base\Http::setETag("{$sBimiSelector}-{$sEncryptedEmail}");
// $sEmail && \MailSo\Base\Http::setETag("{$sBimiSelector}-{$sEncodedEmail}");
if ($sEmail && ($aResult = $this->getAvatar($sEmail, !empty($aBimi[0]), $sBimiSelector))) {
\header("Cache-Control: max-age={$maxAge}, private");
\header('Expires: '.\gmdate('D, j M Y H:i:s', $maxAge + \time()).' UTC');
Expand Down

0 comments on commit 490d808

Please sign in to comment.