@wix/himalaya-cli 0.1456.0 → 0.1458.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +15 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/index.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +35 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/validator.d.ts +42 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/webview-embedded.d.ts +66 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/webview-origin.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +389 -2
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +14 -2
- package/dist/cli.mjs +526 -162
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/component-reference.json +51 -5
- package/guides/component-reference.md +32 -4
- package/package.json +2 -2
|
@@ -9037,7 +9037,9 @@ var screenDescriptorSchema = {
|
|
|
9037
9037
|
maxArgDepth: { type: "integer", minimum: 1 },
|
|
9038
9038
|
maxMessageBytes: { type: "integer", minimum: 1 },
|
|
9039
9039
|
rateLimitMaxCalls: { type: "integer", minimum: 1 },
|
|
9040
|
-
rateLimitWindowMs: { type: "integer", minimum: 1 }
|
|
9040
|
+
rateLimitWindowMs: { type: "integer", minimum: 1 },
|
|
9041
|
+
// RFC 0073 §4.4 — "" is today's T4 floor; "app_content" raises the caps.
|
|
9042
|
+
profile: { type: "string", enum: ["", "app_content"] }
|
|
9041
9043
|
}
|
|
9042
9044
|
},
|
|
9043
9045
|
contentFilterRules: { type: "string" },
|
|
@@ -9069,7 +9071,17 @@ var screenDescriptorSchema = {
|
|
|
9069
9071
|
// RFC 0062 §5.8 — the native session bridge (fields 47/48).
|
|
9070
9072
|
sessionBridge: { type: "boolean" },
|
|
9071
9073
|
hostedSiteAdapter: { type: "string", enum: ["", "wix", "base44"] },
|
|
9072
|
-
hideHostedSiteMenu: { type: "boolean" }
|
|
9074
|
+
hideHostedSiteMenu: { type: "boolean" },
|
|
9075
|
+
// RFC 0073 §4 — embedded worker-bridged WebViews (fields 52–54).
|
|
9076
|
+
originIsolation: { type: "string", enum: ["", "shared", "per_instance"] },
|
|
9077
|
+
framePolicy: { type: "string", enum: ["", "content"] },
|
|
9078
|
+
storageSeed: {
|
|
9079
|
+
type: "object",
|
|
9080
|
+
additionalProperties: false,
|
|
9081
|
+
properties: {
|
|
9082
|
+
snapshotPath: { type: "string" }
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9073
9085
|
}
|
|
9074
9086
|
}
|
|
9075
9087
|
}
|
|
@@ -14124,6 +14136,373 @@ function mergeSiteMenu(nav, site, menu) {
|
|
|
14124
14136
|
function siteMenuDrifted(drift) {
|
|
14125
14137
|
return drift.added.length > 0 || drift.removed.length > 0 || drift.renamed.length > 0;
|
|
14126
14138
|
}
|
|
14139
|
+
|
|
14140
|
+
// ../../core/ts/src/webview-origin.ts
|
|
14141
|
+
function hasDotSegment(absolute) {
|
|
14142
|
+
const schemeEnd = absolute.indexOf("://");
|
|
14143
|
+
if (schemeEnd < 0) return false;
|
|
14144
|
+
const hierarchical = absolute.slice(schemeEnd + 3).split(/[?#]/)[0];
|
|
14145
|
+
const pathStart = hierarchical.indexOf("/");
|
|
14146
|
+
if (pathStart < 0) return false;
|
|
14147
|
+
return hierarchical.slice(pathStart).replace(/%2e/g, ".").split("/").some((seg) => seg === "." || seg === "..");
|
|
14148
|
+
}
|
|
14149
|
+
function rawUrlParts(url) {
|
|
14150
|
+
const trimmed = url.replace(/^[\u0000- ]+|[\u0000- ]+$/g, "");
|
|
14151
|
+
if (/[\\\u0000-\u0020\u007f]/.test(trimmed)) return null;
|
|
14152
|
+
const raw = trimmed;
|
|
14153
|
+
const m = /^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)/i.exec(raw);
|
|
14154
|
+
if (!m) return null;
|
|
14155
|
+
const authority = m[2].slice(m[2].lastIndexOf("@") + 1);
|
|
14156
|
+
let host;
|
|
14157
|
+
let port = "";
|
|
14158
|
+
if (authority.startsWith("[")) {
|
|
14159
|
+
const close = authority.indexOf("]");
|
|
14160
|
+
if (close < 0) return null;
|
|
14161
|
+
host = authority.slice(1, close);
|
|
14162
|
+
const rest = authority.slice(close + 1);
|
|
14163
|
+
if (rest.startsWith(":")) port = rest.slice(1);
|
|
14164
|
+
} else {
|
|
14165
|
+
const colon = authority.lastIndexOf(":");
|
|
14166
|
+
host = colon >= 0 ? authority.slice(0, colon) : authority;
|
|
14167
|
+
port = colon >= 0 ? authority.slice(colon + 1) : "";
|
|
14168
|
+
}
|
|
14169
|
+
host = host.toLowerCase();
|
|
14170
|
+
if (!host) return null;
|
|
14171
|
+
if (port !== "" && !/^\d+$/.test(port)) return null;
|
|
14172
|
+
let parsed;
|
|
14173
|
+
try {
|
|
14174
|
+
parsed = new URL(raw);
|
|
14175
|
+
} catch {
|
|
14176
|
+
return null;
|
|
14177
|
+
}
|
|
14178
|
+
const scheme = m[1].toLowerCase();
|
|
14179
|
+
const bracketless = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
14180
|
+
if (parsed.protocol !== `${scheme}:` || bracketless !== host) return null;
|
|
14181
|
+
if (parsed.port !== "" && parsed.port !== String(Number(port))) return null;
|
|
14182
|
+
return { absolute: raw.toLowerCase(), host, hostPort: port !== "" ? `${host}:${Number(port)}` : host };
|
|
14183
|
+
}
|
|
14184
|
+
function webViewOriginAllowed(url, origins) {
|
|
14185
|
+
const u = rawUrlParts(url);
|
|
14186
|
+
if (!u) return false;
|
|
14187
|
+
const { absolute, host, hostPort } = u;
|
|
14188
|
+
for (const o of origins) {
|
|
14189
|
+
const origin = o.toLowerCase();
|
|
14190
|
+
if (origin.endsWith("/") && origin.includes("://")) {
|
|
14191
|
+
if (hasDotSegment(absolute)) continue;
|
|
14192
|
+
const base = origin.slice(0, -1);
|
|
14193
|
+
if (absolute.startsWith(origin) || absolute === base || absolute.startsWith(base + "?") || absolute.startsWith(base + "#")) return true;
|
|
14194
|
+
continue;
|
|
14195
|
+
}
|
|
14196
|
+
if (origin === host || origin === hostPort) return true;
|
|
14197
|
+
if (absolute.startsWith(origin)) {
|
|
14198
|
+
const next = absolute.charAt(origin.length);
|
|
14199
|
+
if (origin.endsWith("/") || next === "" || next === "/" || next === "?" || next === "#") return true;
|
|
14200
|
+
}
|
|
14201
|
+
if (origin.startsWith("*.") || origin.startsWith(".")) {
|
|
14202
|
+
const suffix = origin.startsWith("*.") ? origin.slice(2) : origin.slice(1);
|
|
14203
|
+
if (host === suffix || host.endsWith(`.${suffix}`)) return true;
|
|
14204
|
+
}
|
|
14205
|
+
}
|
|
14206
|
+
return false;
|
|
14207
|
+
}
|
|
14208
|
+
|
|
14209
|
+
// ../../core/ts/src/webview-embedded.ts
|
|
14210
|
+
var MAX_SIMULTANEOUS_BRIDGED_WEBVIEWS = 4;
|
|
14211
|
+
var WEBVIEW_EMBEDDED_ENUMS = {
|
|
14212
|
+
bridge: ["", "worker"],
|
|
14213
|
+
originIsolation: ["", "shared", "per_instance"],
|
|
14214
|
+
framePolicy: ["", "content"],
|
|
14215
|
+
profile: ["", "app_content"]
|
|
14216
|
+
};
|
|
14217
|
+
var isRec = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14218
|
+
var byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
14219
|
+
function canonicalJson(value) {
|
|
14220
|
+
return canonicalJsonAt(value, /* @__PURE__ */ new Set());
|
|
14221
|
+
}
|
|
14222
|
+
function canonicalJsonAt(value, path) {
|
|
14223
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return JSON.stringify(value);
|
|
14224
|
+
if (typeof value === "number") {
|
|
14225
|
+
if (!Number.isFinite(value)) throw new TypeError(`canonicalJson: ${value} is not representable in JSON`);
|
|
14226
|
+
return String(value);
|
|
14227
|
+
}
|
|
14228
|
+
if (typeof value !== "object") throw new TypeError(`canonicalJson: a ${typeof value} is not JSON data`);
|
|
14229
|
+
if (path.has(value)) throw new TypeError("canonicalJson: a cyclic value is not JSON data");
|
|
14230
|
+
path.add(value);
|
|
14231
|
+
try {
|
|
14232
|
+
if (Array.isArray(value)) {
|
|
14233
|
+
const parts = [];
|
|
14234
|
+
for (let i = 0; i < value.length; i++) {
|
|
14235
|
+
if (!(i in value)) throw new TypeError(`canonicalJson: array element ${i} is a hole, which is not JSON data`);
|
|
14236
|
+
const v = value[i];
|
|
14237
|
+
if (v === void 0) throw new TypeError(`canonicalJson: array element ${i} is undefined, which is not JSON data`);
|
|
14238
|
+
parts.push(canonicalJsonAt(v, path));
|
|
14239
|
+
}
|
|
14240
|
+
return `[${parts.join(",")}]`;
|
|
14241
|
+
}
|
|
14242
|
+
const proto = Object.getPrototypeOf(value);
|
|
14243
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
14244
|
+
throw new TypeError(`canonicalJson: a ${value.constructor?.name ?? "non-plain object"} is not JSON data \u2014 pass a plain object, an array, or a primitive`);
|
|
14245
|
+
}
|
|
14246
|
+
const rec = value;
|
|
14247
|
+
const keys = Object.keys(rec).filter((k) => rec[k] !== void 0).sort(byCodeUnit);
|
|
14248
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJsonAt(rec[k], path)}`).join(",")}}`;
|
|
14249
|
+
} finally {
|
|
14250
|
+
path.delete(value);
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
14253
|
+
function canonicalScreenArgs(args) {
|
|
14254
|
+
if (!args) return "";
|
|
14255
|
+
return Object.keys(args).filter((k) => args[k] !== void 0).sort(byCodeUnit).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(canonicalJson(args[k]))}`).join("&");
|
|
14256
|
+
}
|
|
14257
|
+
function effectiveWebViewInstanceId(screenId, args, componentId, explicitInstanceId) {
|
|
14258
|
+
if (typeof explicitInstanceId === "string" && explicitInstanceId !== "") return explicitInstanceId;
|
|
14259
|
+
return `${screenId}?${canonicalScreenArgs(args)}/${componentId}`;
|
|
14260
|
+
}
|
|
14261
|
+
var isComponent = (v) => isRec(v) && isRec(v.body) && typeof v.body.package === "string" && typeof v.body.type === "string";
|
|
14262
|
+
var typeOf = (c) => {
|
|
14263
|
+
const body = c.body;
|
|
14264
|
+
return body.package === "himalaya" ? body.type : null;
|
|
14265
|
+
};
|
|
14266
|
+
var propsOf = (c) => {
|
|
14267
|
+
const p = c.body.props;
|
|
14268
|
+
return isRec(p) ? p : {};
|
|
14269
|
+
};
|
|
14270
|
+
var hasItemsPath = (props) => typeof props.itemsPath === "string" && props.itemsPath !== "";
|
|
14271
|
+
function childComponents(value, path, inTemplate, out) {
|
|
14272
|
+
if (Array.isArray(value)) {
|
|
14273
|
+
value.forEach((v, i) => childComponents(v, `${path}[${i}]`, inTemplate, out));
|
|
14274
|
+
return;
|
|
14275
|
+
}
|
|
14276
|
+
if (!isRec(value)) return;
|
|
14277
|
+
if (isComponent(value)) {
|
|
14278
|
+
out.push({ c: value, path, inTemplate });
|
|
14279
|
+
return;
|
|
14280
|
+
}
|
|
14281
|
+
for (const [k, v] of Object.entries(value)) childComponents(v, `${path}.${k}`, inTemplate || /Template$/.test(k), out);
|
|
14282
|
+
}
|
|
14283
|
+
function childrenOf(c, path, inTemplate) {
|
|
14284
|
+
const props = propsOf(c);
|
|
14285
|
+
const type = typeOf(c);
|
|
14286
|
+
const boundList = (type === "LazyList" || type === "Grid" || type === "Carousel") && hasItemsPath(props);
|
|
14287
|
+
const out = [];
|
|
14288
|
+
for (const [k, v] of Object.entries(props)) {
|
|
14289
|
+
const templ = inTemplate || /Template$/.test(k) || boundList && k === "items";
|
|
14290
|
+
childComponents(v, `${path}.body.props.${k}`, templ, out);
|
|
14291
|
+
}
|
|
14292
|
+
return out;
|
|
14293
|
+
}
|
|
14294
|
+
function roots(descriptor) {
|
|
14295
|
+
const out = [];
|
|
14296
|
+
for (const key of ["payload", "stickyHeader", "stickyFooter"]) {
|
|
14297
|
+
const v = descriptor[key];
|
|
14298
|
+
if (isComponent(v)) out.push({ c: v, path: key });
|
|
14299
|
+
}
|
|
14300
|
+
return out;
|
|
14301
|
+
}
|
|
14302
|
+
function collectWebViews(descriptor) {
|
|
14303
|
+
const sites = [];
|
|
14304
|
+
const visit = (c, path, inTemplate) => {
|
|
14305
|
+
if (typeOf(c) === "WebView") {
|
|
14306
|
+
sites.push({ path, id: typeof c.id === "string" ? c.id : "", props: propsOf(c), inTemplate });
|
|
14307
|
+
}
|
|
14308
|
+
for (const child of childrenOf(c, path, inTemplate)) visit(child.c, child.path, child.inTemplate);
|
|
14309
|
+
};
|
|
14310
|
+
for (const r of roots(descriptor)) visit(r.c, r.path, false);
|
|
14311
|
+
return sites;
|
|
14312
|
+
}
|
|
14313
|
+
var isBridged = (props) => props.bridge === "worker";
|
|
14314
|
+
var configOf = (props) => isRec(props.config) ? props.config : {};
|
|
14315
|
+
var isPerInstance = (props) => configOf(props).originIsolation === "per_instance";
|
|
14316
|
+
var isLiteral = (v) => typeof v === "string" && v !== "" && !v.startsWith("$");
|
|
14317
|
+
var isItemDerived = (v) => typeof v === "string" && /\$item\.[A-Za-z0-9_]/.test(v);
|
|
14318
|
+
function mountable(c, inTemplate = false) {
|
|
14319
|
+
const type = typeOf(c);
|
|
14320
|
+
const props = propsOf(c);
|
|
14321
|
+
const repeated = (n) => inTemplate && n > 0 ? Infinity : n;
|
|
14322
|
+
if (type === "WebView") return repeated(isBridged(props) ? 1 : 0);
|
|
14323
|
+
if (type === "LazyList") {
|
|
14324
|
+
let slots = 0;
|
|
14325
|
+
for (const [k, v] of Object.entries(props)) {
|
|
14326
|
+
if (k === "items") continue;
|
|
14327
|
+
const found = [];
|
|
14328
|
+
childComponents(v, "", inTemplate || /Template$/.test(k), found);
|
|
14329
|
+
for (const child of found) slots += mountable(child.c, child.inTemplate);
|
|
14330
|
+
}
|
|
14331
|
+
return slots;
|
|
14332
|
+
}
|
|
14333
|
+
if (type === "Carousel" || type === "Pager") {
|
|
14334
|
+
const pages = type === "Carousel" ? props.items : props.pages;
|
|
14335
|
+
const perPage = Array.isArray(pages) ? pages.map((p) => isComponent(p) ? mountable(p) : 0) : [];
|
|
14336
|
+
const window = typeof props.liveWindow === "number" && props.liveWindow > 0 ? Math.floor(props.liveWindow) : 0;
|
|
14337
|
+
const bound = type === "Carousel" && hasItemsPath(props);
|
|
14338
|
+
if (bound) {
|
|
14339
|
+
const template = perPage[0] ?? 0;
|
|
14340
|
+
if (template === 0) return 0;
|
|
14341
|
+
return repeated(window > 0 ? template * (2 * window + 1) : Infinity);
|
|
14342
|
+
}
|
|
14343
|
+
if (window > 0) return repeated(perPage.sort((a, b) => b - a).slice(0, 2 * window + 1).reduce((a, b) => a + b, 0));
|
|
14344
|
+
return repeated(perPage.reduce((a, b) => a + b, 0));
|
|
14345
|
+
}
|
|
14346
|
+
if (type === "Grid" && hasItemsPath(props)) {
|
|
14347
|
+
const items = props.items;
|
|
14348
|
+
const template = Array.isArray(items) && isComponent(items[0]) ? mountable(items[0]) : 0;
|
|
14349
|
+
return template === 0 ? 0 : Infinity;
|
|
14350
|
+
}
|
|
14351
|
+
let total = 0;
|
|
14352
|
+
for (const child of childrenOf(c, "", inTemplate)) total += mountable(child.c, child.inTemplate);
|
|
14353
|
+
return total;
|
|
14354
|
+
}
|
|
14355
|
+
function webViewEmbeddedFindings(descriptor) {
|
|
14356
|
+
if (!isRec(descriptor)) return [];
|
|
14357
|
+
const out = [];
|
|
14358
|
+
const screenConfig = isRec(descriptor.webViewConfig) ? descriptor.webViewConfig : {};
|
|
14359
|
+
if (screenConfig.storageSeed !== void 0 && screenConfig.originIsolation !== "per_instance") {
|
|
14360
|
+
out.push({
|
|
14361
|
+
severity: "error",
|
|
14362
|
+
rule: "storage_seed_requires_per_instance",
|
|
14363
|
+
path: "webViewConfig.storageSeed",
|
|
14364
|
+
message: `the screen's webViewConfig sets storageSeed without originIsolation: "per_instance" \u2014 a seed migrates into a per-instance origin only (RFC 0073 \xA74.2)`
|
|
14365
|
+
});
|
|
14366
|
+
}
|
|
14367
|
+
const sites = collectWebViews(descriptor);
|
|
14368
|
+
if (sites.length === 0) return out;
|
|
14369
|
+
const screenId = typeof descriptor.screenId === "string" ? descriptor.screenId : "";
|
|
14370
|
+
const lb = isRec(descriptor.logicBundle) ? descriptor.logicBundle : null;
|
|
14371
|
+
const caps = lb && Array.isArray(lb.declaredCapabilities) ? lb.declaredCapabilities : [];
|
|
14372
|
+
const seen = /* @__PURE__ */ new Map();
|
|
14373
|
+
for (const s of sites) {
|
|
14374
|
+
const { props, path } = s;
|
|
14375
|
+
const config = configOf(props);
|
|
14376
|
+
const bridged = isBridged(props);
|
|
14377
|
+
const security = isRec(config.bridgeSecurity) ? config.bridgeSecurity : {};
|
|
14378
|
+
for (const [field, value, prop] of [
|
|
14379
|
+
["bridge", props.bridge, "bridge"],
|
|
14380
|
+
["originIsolation", config.originIsolation, "config.originIsolation"],
|
|
14381
|
+
["framePolicy", config.framePolicy, "config.framePolicy"],
|
|
14382
|
+
["profile", security.profile, "config.bridgeSecurity.profile"]
|
|
14383
|
+
]) {
|
|
14384
|
+
const allowed = WEBVIEW_EMBEDDED_ENUMS[field];
|
|
14385
|
+
if (value === void 0 || typeof value === "string" && allowed.includes(value)) continue;
|
|
14386
|
+
out.push({
|
|
14387
|
+
severity: "error",
|
|
14388
|
+
rule: "unknown_enum_value",
|
|
14389
|
+
path: `${path}.body.props.${prop}`,
|
|
14390
|
+
message: `WebView "${s.id}" sets ${prop}: ${JSON.stringify(value)} \u2014 not one of ${allowed.map((v) => JSON.stringify(v)).join(" | ")}; the hosts read an unknown value as ${JSON.stringify(allowed[0])} (RFC 0073 \xA74)`
|
|
14391
|
+
});
|
|
14392
|
+
}
|
|
14393
|
+
if (bridged && (!lb || !caps.includes("webview"))) {
|
|
14394
|
+
out.push({
|
|
14395
|
+
severity: "error",
|
|
14396
|
+
rule: "bridge_requires_worker",
|
|
14397
|
+
path: `${path}.body.props.bridge`,
|
|
14398
|
+
message: !lb ? `WebView "${s.id}" sets bridge: "worker" but the screen has no logicBundle \u2014 the channel connects the page to the screen's worker (RFC 0073 \xA74.6)` : `WebView "${s.id}" sets bridge: "worker" but logicBundle.declaredCapabilities lacks "webview" (RFC 0073 \xA73, \xA74.6)`
|
|
14399
|
+
});
|
|
14400
|
+
}
|
|
14401
|
+
if ((bridged || isPerInstance(props)) && s.inTemplate && !isItemDerived(props.instanceId)) {
|
|
14402
|
+
out.push({
|
|
14403
|
+
severity: "error",
|
|
14404
|
+
rule: "template_instance_id",
|
|
14405
|
+
path: `${path}.body.props.instanceId`,
|
|
14406
|
+
message: `WebView "${s.id}" is ${bridged ? "bridged" : `per_instance`} inside an item template but its instanceId is not derived from the item (e.g. "$item.id") \u2014 every row would share one ${bridged ? "channel and one origin" : "origin and its storage"} (RFC 0073 \xA74.1)`
|
|
14407
|
+
});
|
|
14408
|
+
}
|
|
14409
|
+
if ((bridged || isPerInstance(props)) && !s.inTemplate) {
|
|
14410
|
+
const explicit = props.instanceId;
|
|
14411
|
+
const effective = isLiteral(explicit) ? explicit : explicit === void 0 || explicit === "" ? effectiveWebViewInstanceId(screenId, void 0, s.id) : null;
|
|
14412
|
+
if (effective !== null) {
|
|
14413
|
+
const first = seen.get(effective);
|
|
14414
|
+
if (first !== void 0) {
|
|
14415
|
+
out.push({
|
|
14416
|
+
severity: "error",
|
|
14417
|
+
rule: "duplicate_instance_id",
|
|
14418
|
+
path: `${path}.body.props.instanceId`,
|
|
14419
|
+
message: `WebView "${s.id}" has the same effective instance_id "${effective}" as ${first} \u2014 two pages on one screen would share a channel and storage (RFC 0073 \xA74.1)`
|
|
14420
|
+
});
|
|
14421
|
+
} else {
|
|
14422
|
+
seen.set(effective, path);
|
|
14423
|
+
}
|
|
14424
|
+
}
|
|
14425
|
+
}
|
|
14426
|
+
if (config.storageSeed !== void 0 && config.originIsolation !== "per_instance") {
|
|
14427
|
+
out.push({
|
|
14428
|
+
severity: "error",
|
|
14429
|
+
rule: "storage_seed_requires_per_instance",
|
|
14430
|
+
path: `${path}.body.props.config.storageSeed`,
|
|
14431
|
+
message: `WebView "${s.id}" sets config.storageSeed without originIsolation: "per_instance" \u2014 a seed migrates into a per-instance origin only (RFC 0073 \xA74.2)`
|
|
14432
|
+
});
|
|
14433
|
+
}
|
|
14434
|
+
if (config.originIsolation === "per_instance" && typeof props.url === "string" && props.url !== "") {
|
|
14435
|
+
out.push({
|
|
14436
|
+
severity: "warning",
|
|
14437
|
+
rule: "per_instance_with_url",
|
|
14438
|
+
path: `${path}.body.props.config.originIsolation`,
|
|
14439
|
+
message: `WebView "${s.id}" sets originIsolation: "per_instance" with a url \u2014 per-instance origins apply to inlineHtml only; a url keeps its own origin (RFC 0073 \xA74.2)`
|
|
14440
|
+
});
|
|
14441
|
+
}
|
|
14442
|
+
if (props.fill === true && (props.height !== void 0 || props.aspectRatio !== void 0 || props.heightPath !== void 0)) {
|
|
14443
|
+
out.push({
|
|
14444
|
+
severity: "warning",
|
|
14445
|
+
rule: "fill_with_height",
|
|
14446
|
+
path: `${path}.body.props.fill`,
|
|
14447
|
+
message: `WebView "${s.id}" sets fill together with height / aspectRatio / heightPath \u2014 fill wins and the others are ignored (RFC 0073 \xA74)`
|
|
14448
|
+
});
|
|
14449
|
+
}
|
|
14450
|
+
if (bridged && isLiteral(props.url)) {
|
|
14451
|
+
const allowed = Array.isArray(config.allowedOrigins) ? config.allowedOrigins.filter((o) => typeof o === "string") : [];
|
|
14452
|
+
if (allowed.length > 0 && !webViewOriginAllowed(props.url, allowed)) {
|
|
14453
|
+
out.push({
|
|
14454
|
+
severity: "warning",
|
|
14455
|
+
rule: "bridged_url_not_allowed",
|
|
14456
|
+
path: `${path}.body.props.url`,
|
|
14457
|
+
message: `WebView "${s.id}" is bridged to a url whose origin is not listed in config.allowedOrigins \u2014 list it to assert the page is app-controlled; web refuses the bridge otherwise (RFC 0073 \xA74.6, \xA78)`
|
|
14458
|
+
});
|
|
14459
|
+
}
|
|
14460
|
+
}
|
|
14461
|
+
}
|
|
14462
|
+
let total = 0;
|
|
14463
|
+
let crossedAt = "";
|
|
14464
|
+
for (const r of roots(descriptor)) {
|
|
14465
|
+
total += mountable(r.c);
|
|
14466
|
+
if (crossedAt === "" && total > MAX_SIMULTANEOUS_BRIDGED_WEBVIEWS) crossedAt = r.path;
|
|
14467
|
+
}
|
|
14468
|
+
if (crossedAt !== "") {
|
|
14469
|
+
out.push({
|
|
14470
|
+
severity: "warning",
|
|
14471
|
+
rule: "too_many_bridged",
|
|
14472
|
+
path: crossedAt,
|
|
14473
|
+
message: `this screen can mount ${Number.isFinite(total) ? total : "an unbounded number of"} worker-bridged WebViews at once (more than ${MAX_SIMULTANEOUS_BRIDGED_WEBVIEWS}) \u2014 put them in a LazyList or a Carousel/Pager with liveWindow (RFC 0073 \xA74.5)`
|
|
14474
|
+
});
|
|
14475
|
+
}
|
|
14476
|
+
return out;
|
|
14477
|
+
}
|
|
14478
|
+
function webViewEmbeddedAppFindings(screens) {
|
|
14479
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14480
|
+
for (const { screenId, descriptor } of screens) {
|
|
14481
|
+
if (!isRec(descriptor)) continue;
|
|
14482
|
+
for (const s of collectWebViews(descriptor)) {
|
|
14483
|
+
if (!isPerInstance(s.props) || !isLiteral(s.props.instanceId)) continue;
|
|
14484
|
+
const content = [s.props.inlineHtml, s.props.url].find(isLiteral);
|
|
14485
|
+
if (content === void 0) continue;
|
|
14486
|
+
const list = byId.get(s.props.instanceId) ?? [];
|
|
14487
|
+
list.push({ screenId, path: s.path, content });
|
|
14488
|
+
byId.set(s.props.instanceId, list);
|
|
14489
|
+
}
|
|
14490
|
+
}
|
|
14491
|
+
const out = [];
|
|
14492
|
+
for (const [id, uses] of byId) {
|
|
14493
|
+
const screensSeen = new Set(uses.map((u) => u.screenId));
|
|
14494
|
+
const contents = new Set(uses.map((u) => u.content));
|
|
14495
|
+
if (screensSeen.size < 2 || contents.size < 2) continue;
|
|
14496
|
+
out.push({
|
|
14497
|
+
severity: "warning",
|
|
14498
|
+
rule: "instance_id_reused_across_screens",
|
|
14499
|
+
screenId: uses[0].screenId,
|
|
14500
|
+
path: `${uses[0].path}.body.props.instanceId`,
|
|
14501
|
+
message: `per_instance instanceId "${id}" is bound to different content on screens ${[...screensSeen].join(", ")} \u2014 they would share one origin and its storage (RFC 0073 \xA74.1)`
|
|
14502
|
+
});
|
|
14503
|
+
}
|
|
14504
|
+
return out;
|
|
14505
|
+
}
|
|
14127
14506
|
export {
|
|
14128
14507
|
ALL_ORIENTATIONS,
|
|
14129
14508
|
APP_ID_PATTERN,
|
|
@@ -14156,6 +14535,7 @@ export {
|
|
|
14156
14535
|
LAUNCH_MANIFEST_SCHEMA,
|
|
14157
14536
|
LAUNCH_NAMESPACE,
|
|
14158
14537
|
LINK_MOUNT_PATHS,
|
|
14538
|
+
MAX_SIMULTANEOUS_BRIDGED_WEBVIEWS,
|
|
14159
14539
|
OAUTH_IDP_ORIGINS,
|
|
14160
14540
|
ORIGIN_PARAM_KEYS,
|
|
14161
14541
|
PACKAGE_SHARE_ID_PATTERN,
|
|
@@ -14195,6 +14575,7 @@ export {
|
|
|
14195
14575
|
SYSTEM_FONT_FAMILY,
|
|
14196
14576
|
TONAL_ELEVATION_ALPHA,
|
|
14197
14577
|
UNIVERSAL_LAUNCH_NAMESPACES,
|
|
14578
|
+
WEBVIEW_EMBEDDED_ENUMS,
|
|
14198
14579
|
WIDGET_COMPONENTS,
|
|
14199
14580
|
WIDGET_FAMILIES,
|
|
14200
14581
|
WIDGET_VOCABULARY,
|
|
@@ -14212,6 +14593,8 @@ export {
|
|
|
14212
14593
|
buildTokenRegistry,
|
|
14213
14594
|
buttonHitSlop,
|
|
14214
14595
|
buttonVariant,
|
|
14596
|
+
canonicalJson,
|
|
14597
|
+
canonicalScreenArgs,
|
|
14215
14598
|
chassisOuterSize,
|
|
14216
14599
|
checkTierCaps,
|
|
14217
14600
|
chooseCandidate,
|
|
@@ -14226,6 +14609,7 @@ export {
|
|
|
14226
14609
|
defineSitePageScreen,
|
|
14227
14610
|
defineSiteTab,
|
|
14228
14611
|
effectiveFieldRule,
|
|
14612
|
+
effectiveWebViewInstanceId,
|
|
14229
14613
|
errorMessageFor,
|
|
14230
14614
|
evaluate,
|
|
14231
14615
|
evaluateAll,
|
|
@@ -14313,6 +14697,9 @@ export {
|
|
|
14313
14697
|
validateWorkerSubtree,
|
|
14314
14698
|
validators,
|
|
14315
14699
|
viewTargetRenderMode,
|
|
14700
|
+
webViewEmbeddedAppFindings,
|
|
14701
|
+
webViewEmbeddedFindings,
|
|
14702
|
+
webViewOriginAllowed,
|
|
14316
14703
|
widgetSnapshotSchema,
|
|
14317
14704
|
workerSubtreePolicyErrors,
|
|
14318
14705
|
workerSubtreeSha256
|
|
@@ -7729,7 +7729,9 @@ var screenDescriptorSchema = {
|
|
|
7729
7729
|
maxArgDepth: { type: "integer", minimum: 1 },
|
|
7730
7730
|
maxMessageBytes: { type: "integer", minimum: 1 },
|
|
7731
7731
|
rateLimitMaxCalls: { type: "integer", minimum: 1 },
|
|
7732
|
-
rateLimitWindowMs: { type: "integer", minimum: 1 }
|
|
7732
|
+
rateLimitWindowMs: { type: "integer", minimum: 1 },
|
|
7733
|
+
// RFC 0073 §4.4 — "" is today's T4 floor; "app_content" raises the caps.
|
|
7734
|
+
profile: { type: "string", enum: ["", "app_content"] }
|
|
7733
7735
|
}
|
|
7734
7736
|
},
|
|
7735
7737
|
contentFilterRules: { type: "string" },
|
|
@@ -7761,7 +7763,17 @@ var screenDescriptorSchema = {
|
|
|
7761
7763
|
// RFC 0062 §5.8 — the native session bridge (fields 47/48).
|
|
7762
7764
|
sessionBridge: { type: "boolean" },
|
|
7763
7765
|
hostedSiteAdapter: { type: "string", enum: ["", "wix", "base44"] },
|
|
7764
|
-
hideHostedSiteMenu: { type: "boolean" }
|
|
7766
|
+
hideHostedSiteMenu: { type: "boolean" },
|
|
7767
|
+
// RFC 0073 §4 — embedded worker-bridged WebViews (fields 52–54).
|
|
7768
|
+
originIsolation: { type: "string", enum: ["", "shared", "per_instance"] },
|
|
7769
|
+
framePolicy: { type: "string", enum: ["", "content"] },
|
|
7770
|
+
storageSeed: {
|
|
7771
|
+
type: "object",
|
|
7772
|
+
additionalProperties: false,
|
|
7773
|
+
properties: {
|
|
7774
|
+
snapshotPath: { type: "string" }
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7765
7777
|
}
|
|
7766
7778
|
}
|
|
7767
7779
|
}
|