fix(custom-widgets): address v2 merge review (#6446)
This commit is contained in:
1 parent
6aceb2fd1f
commit
df967751f6
123 files changed
+8632
-1477
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { validateCustomJsxTemplate } from "../jsx/analyzer";
|
||||
import { CUSTOM_JSX_BINDING_IDENTIFIER_PATTERN } from "../jsx/policy";
|
||||
import { validateCredentialFreeExport, validatePrototypeKeys } from "./definition-security";
|
||||
import { customWidgetOptionsSchema } from "./options-schema";
|
||||
import {
|
||||
@@ -20,7 +21,10 @@ export const customWidgetBindingIdentifierSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(64)
|
||||
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/u, "Binding names must start with a letter and contain letters, numbers, - or _");
|
||||
.regex(
|
||||
CUSTOM_JSX_BINDING_IDENTIFIER_PATTERN,
|
||||
"Binding names must start with a letter and contain letters, numbers, - or _",
|
||||
);
|
||||
|
||||
export const customJsxTemplateSchema = z
|
||||
.string()
|
||||
@@ -77,8 +81,8 @@ export const customWidgetDefinitionSchema = z
|
||||
})
|
||||
.superRefine((definition, ctx) => {
|
||||
validateRequests(definition, ctx);
|
||||
validateCaseInsensitiveSourceIds(definition.sources, ctx);
|
||||
validateTemplateRequests(definition.template, definition.requests, ctx);
|
||||
validateTemplateBindings(definition.template, ctx);
|
||||
validateCredentialFreeExport(definition, ctx);
|
||||
validatePrototypeKeys(definition, ctx);
|
||||
});
|
||||
@@ -144,6 +148,23 @@ function validateRequests(definition: Definition, ctx: z.RefinementCtx) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateCaseInsensitiveSourceIds(sources: Definition["sources"], ctx: z.RefinementCtx) {
|
||||
const seen = new Map<string, string>();
|
||||
for (const sourceId of Object.keys(sources)) {
|
||||
const normalized = sourceId.toLowerCase();
|
||||
const existing = seen.get(normalized);
|
||||
if (existing) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
path: ["sources", sourceId],
|
||||
message: `Source ID '${sourceId}' conflicts case-insensitively with '${existing}'`,
|
||||
});
|
||||
} else {
|
||||
seen.set(normalized, sourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateTemplateRequests(template: string, requests: Record<string, CustomJsxRequest>, ctx: z.RefinementCtx) {
|
||||
for (const match of template.matchAll(/<(SubFetch|ActionButton|ToggleSwitch)\b([^>]*)>/gu)) {
|
||||
const component = match[1] as "SubFetch" | "ActionButton" | "ToggleSwitch";
|
||||
@@ -169,15 +190,6 @@ function validateTemplateRequests(template: string, requests: Record<string, Cus
|
||||
}
|
||||
}
|
||||
|
||||
function validateTemplateBindings(template: string, ctx: z.RefinementCtx) {
|
||||
for (const match of template.matchAll(/<[A-Z][A-Za-z0-9.]*(?:\s[^<>]*?)?\bbind\s*=\s*(?:"([^"]+)"|'([^']+)'|\{)/gu)) {
|
||||
const inputName = match[1] ?? match[2];
|
||||
if (!inputName) ctx.addIssue({ code: "custom", path: ["template"], message: "bind must use a literal input name" });
|
||||
else if (!customWidgetBindingIdentifierSchema.safeParse(inputName).success)
|
||||
ctx.addIssue({ code: "custom", path: ["template"], message: `Invalid bind input name '${inputName}'` });
|
||||
}
|
||||
}
|
||||
|
||||
export type HomarrCustomWidgetV2 = z.infer<typeof customWidgetDefinitionSchema>;
|
||||
export type HomarrCustomWidgetV2Input = z.input<typeof customWidgetDefinitionSchema>;
|
||||
export const customWidgetCreateSchema = customWidgetDefinitionSchema.safeExtend({
|
||||
|
||||
Reference in new issue
Block a user