fix: load avatars from Gravatar mirrors when Gravatar is unreachable
This commit is contained in:
1 parent
eed1b3989b
commit
ec9d5e78f7
2 files changed
+43
-4
No files matched your search
@@ -184,7 +184,7 @@ export function SettingsPage() {
|
||||
? <button type="button" className="button-secondary" aria-describedby="settings-name-description" onClick={loadProfile}>重试</button>
|
||||
: <input id="settings-name" aria-describedby="settings-name-description" value={draft.name} onChange={event => update('name', event.target.value)} disabled={!member} required maxLength={80} autoComplete="nickname" />}
|
||||
</SettingRow>
|
||||
<SettingRow id="settings-email" title="邮箱" description="只用来显示 Gravatar 头像,服务器不会保存邮箱地址;留空就不显示头像。" field>
|
||||
<SettingRow id="settings-email" title="邮箱" description="只用来显示 Gravatar 头像,访问不到 Gravatar 时改用镜像 WeAvatar 或 Cravatar;服务器不会保存邮箱地址,留空就不显示头像。" field>
|
||||
<input id="settings-email" aria-describedby="settings-email-description" type="email" value={draft.email} onChange={event => update('email', event.target.value)} disabled={!member} maxLength={320} autoComplete="email" placeholder="name@example.com" />
|
||||
</SettingRow>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,50 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Store } from '@/shared/lib/store'
|
||||
import { useStore } from '@/shared/lib/use-store'
|
||||
|
||||
const gravatar = 'https://www.gravatar.com/avatar/'
|
||||
const mirrors = ['https://weavatar.com/avatar/', 'https://cravatar.cn/avatar/']
|
||||
const gravatarTimeoutMs = 3000
|
||||
const mirrorTimeoutMs = 5000
|
||||
|
||||
// 有的网络访问不到 Gravatar,要改用镜像。头像加载失败分不清是没有头像(d=404)还是网络不通,所以另外探测:
|
||||
// no-cors 请求拿到任何响应都算能访问。探测的地址要返回图片,其他类型的不透明响应可能被浏览器当作网络错误。
|
||||
// 丢包的网络要等连接超时才会失败,所以限时。
|
||||
function reachable(base: string, timeoutMs: number): Promise<string> {
|
||||
return fetch(`${base}?s=1&d=blank`, { mode: 'no-cors', signal: AbortSignal.timeout(timeoutMs) }).then(() => base)
|
||||
}
|
||||
|
||||
// 镜像响应慢,逐个探测既慢又容易超时,所以同时探测、用先响应的那个;都不通时只显示首字。
|
||||
class AvatarBase extends Store<{ url: string | null }> {
|
||||
#probing = false
|
||||
|
||||
constructor() {
|
||||
super({ url: null })
|
||||
}
|
||||
|
||||
probe(): void {
|
||||
if (this.#probing)
|
||||
return
|
||||
this.#probing = true
|
||||
void reachable(gravatar, gravatarTimeoutMs)
|
||||
.catch(() => Promise.any(mirrors.map(base => reachable(base, mirrorTimeoutMs))))
|
||||
.then(url => this.setState({ url }), () => {})
|
||||
}
|
||||
}
|
||||
|
||||
const avatarBase = new AvatarBase()
|
||||
|
||||
export function Avatar({ hash, name, className = '' }: { hash: string, name: string, className?: string }) {
|
||||
const base = useStore(avatarBase, s => s.url)
|
||||
const [failedHash, setFailedHash] = useState<string>()
|
||||
useEffect(() => {
|
||||
if (hash)
|
||||
avatarBase.probe()
|
||||
}, [hash])
|
||||
return (
|
||||
<span className={`avatar ${className}`} aria-hidden="true">
|
||||
{hash && hash !== failedHash
|
||||
? <img src={`https://www.gravatar.com/avatar/${hash}?s=96&d=404`} alt="" loading="lazy" decoding="async" referrerPolicy="no-referrer" onError={() => setFailedHash(hash)} />
|
||||
{base && hash && hash !== failedHash
|
||||
? <img src={`${base}${hash}?s=96&d=404`} alt="" loading="lazy" decoding="async" referrerPolicy="no-referrer" onError={() => setFailedHash(hash)} />
|
||||
: Array.from(name.trim())[0] ?? '?'}
|
||||
</span>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user