fix(openflare): improve SWOfflineDomains validation and snapshot diff logic

This commit is contained in:
ryan
2026-08-08 20:52:07 +08:00
parent 6882481a56
commit 9797fcdb2f
8 changed files with 42 additions and 6 deletions
+5
View File
@@ -26,6 +26,11 @@ sidebar: false
- 支持 Service Worker 离线兜底:为启用 HTTPS 的网站下发 Service Worker 并缓存离线联系页,域名无法访问时浏览器展示联系站长页面,减少用户流失。可指定生效域名范围(仅对选中的 HTTPS 域名生效),配置位于「响应页面」-「联系页」,可在版本发布中批量生效。 - 支持 Service Worker 离线兜底:为启用 HTTPS 的网站下发 Service Worker 并缓存离线联系页,域名无法访问时浏览器展示联系站长页面,减少用户流失。可指定生效域名范围(仅对选中的 HTTPS 域名生效),配置位于「响应页面」-「联系页」,可在版本发布中批量生效。
### 修复
- 修复 `SWOfflineDomains` 校验与版本比对逻辑:校验器严格拒绝 JSON `null` 输入,配置版本 Diff 比对改用 JSON 序列化避免 `strings.Join` 逗号分隔符歧义。
- 修复 HTML 编辑器预览标题:`HtmlEditorWorkspace` 支持传入 `previewTitle`,离线联系页预览使用「离线联系页实时预览」。
### 改进 ### 改进
- 响应页面统一:将源站错误页与 Service Worker 离线兜底联系页整合至统一的「响应页面」(`/responses`)标签页中,移除原 `/error-pages` 独立路由。 - 响应页面统一:将源站错误页与 Service Worker 离线兜底联系页整合至统一的「响应页面」(`/responses`)标签页中,移除原 `/error-pages` 独立路由。
@@ -194,6 +194,7 @@ export function ContactPageTab({
preview={(html) => html} preview={(html) => html}
footerHint={null} footerHint={null}
showPreviewLink={false} showPreviewLink={false}
previewTitle='离线联系页实时预览'
/> />
</CardContent> </CardContent>
</Card> </Card>
@@ -42,9 +42,7 @@ export function ErrorPageTab({
optionMap: Record<string, string>; optionMap: Record<string, string>;
}) { }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [fields, setFields] = useState<ErrorPageFields>( const [fields, setFields] = useState<ErrorPageFields>(defaultErrorPageFields);
defaultErrorPageFields,
);
const [tagError, setTagError] = useState<string | null>(null); const [tagError, setTagError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
@@ -21,6 +21,7 @@ type HtmlEditorWorkspaceProps = {
preview?: (html: string) => string; preview?: (html: string) => string;
footerHint?: React.ReactNode; footerHint?: React.ReactNode;
showPreviewLink?: boolean; showPreviewLink?: boolean;
previewTitle?: string;
}; };
/** /**
@@ -38,6 +39,7 @@ export function HtmlEditorWorkspace({
</> </>
), ),
showPreviewLink = true, showPreviewLink = true,
previewTitle = '源站错误页实时预览',
}: HtmlEditorWorkspaceProps) { }: HtmlEditorWorkspaceProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [editorWidthPercent, setEditorWidthPercent] = useState(48); const [editorWidthPercent, setEditorWidthPercent] = useState(48);
@@ -145,7 +147,7 @@ export function HtmlEditorWorkspace({
</div> </div>
<div className='flex-1 min-h-0 overflow-hidden bg-background'> <div className='flex-1 min-h-0 overflow-hidden bg-background'>
<iframe <iframe
title='源站错误页实时预览' title={previewTitle}
sandbox='' sandbox=''
srcDoc={previewSrcDoc} srcDoc={previewSrcDoc}
className='h-full w-full border-0 bg-background' className='h-full w-full border-0 bg-background'
@@ -540,7 +540,7 @@ func diffOpenRestyOptionDetails(left openRestyConfigSnapshot, right openRestyCon
appendIfChanged("OriginErrorPageGetOnly", fmt.Sprintf("%t", left.OriginErrorPageGetOnly), fmt.Sprintf("%t", right.OriginErrorPageGetOnly)) appendIfChanged("OriginErrorPageGetOnly", fmt.Sprintf("%t", left.OriginErrorPageGetOnly), fmt.Sprintf("%t", right.OriginErrorPageGetOnly))
appendIfChanged("SWOfflineEnabled", fmt.Sprintf("%t", left.SWOfflineEnabled), fmt.Sprintf("%t", right.SWOfflineEnabled)) appendIfChanged("SWOfflineEnabled", fmt.Sprintf("%t", left.SWOfflineEnabled), fmt.Sprintf("%t", right.SWOfflineEnabled))
appendIfChanged("SWOfflineHTML", left.SWOfflineHTML, right.SWOfflineHTML) appendIfChanged("SWOfflineHTML", left.SWOfflineHTML, right.SWOfflineHTML)
appendIfChanged("SWOfflineDomains", strings.Join(left.SWOfflineDomains, ","), strings.Join(right.SWOfflineDomains, ",")) appendIfChanged("SWOfflineDomains", encodeSWOfflineDomains(left.SWOfflineDomains), encodeSWOfflineDomains(right.SWOfflineDomains))
return changes return changes
} }
@@ -555,6 +555,17 @@ func encodeOriginErrorPageStatusCodes(tags []string) string {
return string(payload) return string(payload)
} }
func encodeSWOfflineDomains(domains []string) string {
if len(domains) == 0 {
return ""
}
payload, err := json.Marshal(domains)
if err != nil {
return strings.Join(domains, ",")
}
return string(payload)
}
func extractOptionDiffKeys(details []ConfigOptionDiffItem) []string { func extractOptionDiffKeys(details []ConfigOptionDiffItem) []string {
keys := make([]string, 0, len(details)) keys := make([]string, 0, len(details))
for _, item := range details { for _, item := range details {
@@ -101,3 +101,21 @@ func TestDiffOpenRestyOptionDetailsOriginErrorPage(t *testing.T) {
assert.Equal(t, "", keys["OriginErrorPageHTML"].PreviousValue) assert.Equal(t, "", keys["OriginErrorPageHTML"].PreviousValue)
assert.Equal(t, "<p>x</p>", keys["OriginErrorPageHTML"].CurrentValue) assert.Equal(t, "<p>x</p>", keys["OriginErrorPageHTML"].CurrentValue)
} }
func TestDiffOpenRestyOptionDetailsSWOfflineDomains(t *testing.T) {
t.Parallel()
left := openRestyConfigSnapshot{
SWOfflineDomains: []string{"a.com,b.com"},
}
right := openRestyConfigSnapshot{
SWOfflineDomains: []string{"a.com", "b.com"},
}
details := diffOpenRestyOptionDetails(left, right)
keys := make(map[string]ConfigOptionDiffItem, len(details))
for _, item := range details {
keys[item.Key] = item
}
assert.Equal(t, `["a.com,b.com"]`, keys["SWOfflineDomains"].PreviousValue)
assert.Equal(t, `["a.com","b.com"]`, keys["SWOfflineDomains"].CurrentValue)
}
@@ -260,7 +260,7 @@ func validateSWOfflineHTML(key, value string) error {
func validateSWOfflineDomains(key, value string) error { func validateSWOfflineDomains(key, value string) error {
var domains []string var domains []string
if err := json.Unmarshal([]byte(value), &domains); err != nil { if err := json.Unmarshal([]byte(value), &domains); err != nil || domains == nil {
return fmt.Errorf("%s 必须为 JSON 字符串数组", key) return fmt.Errorf("%s 必须为 JSON 字符串数组", key)
} }
if len(domains) > maxSWOfflineDomains { if len(domains) > maxSWOfflineDomains {
@@ -106,6 +106,7 @@ func TestValidateSWOfflineDomains(t *testing.T) {
{"single", `["example.com"]`, true}, {"single", `["example.com"]`, true},
{"multiple", `["example.com","api.example.com"]`, true}, {"multiple", `["example.com","api.example.com"]`, true},
{"invalid json", `not-json`, false}, {"invalid json", `not-json`, false},
{"null", "null", false},
{"empty element", `[""]`, false}, {"empty element", `[""]`, false},
{"duplicate", `["example.com","example.com"]`, false}, {"duplicate", `["example.com","example.com"]`, false},
{"whitespace dedup", `[" Example.com ","example.com"]`, false}, {"whitespace dedup", `[" Example.com ","example.com"]`, false},