mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-07 16:16:37 +08:00
fix(openflare): improve SWOfflineDomains validation and snapshot diff logic
This commit is contained in:
@@ -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},
|
||||||
|
|||||||
Reference in New Issue
Block a user