@kotao/storefront 0.2.4 → 0.2.6
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/README.md +50 -2
- package/dist/bridge-ZKbKqgFd.d.ts +239 -0
- package/dist/cart-documents-BLavRDiu.d.ts +89 -0
- package/dist/cart-documents.d.ts +1 -2
- package/dist/cart-documents.js +1 -1
- package/dist/{chunk-M7BPQ54D.js → chunk-2MRB6SMP.js} +4 -3
- package/dist/chunk-2MRB6SMP.js.map +1 -0
- package/dist/chunk-CKGI3Y7L.js +19 -0
- package/dist/chunk-CKGI3Y7L.js.map +1 -0
- package/dist/{chunk-4TEFGD42.js → chunk-EG34HLSX.js} +209 -162
- package/dist/chunk-EG34HLSX.js.map +1 -0
- package/dist/chunk-O6YZEY7X.js +1382 -0
- package/dist/chunk-O6YZEY7X.js.map +1 -0
- package/dist/{chunk-7MTCGXIG.js → chunk-R4N565GX.js} +8 -4
- package/dist/chunk-R4N565GX.js.map +1 -0
- package/dist/{chunk-XMTWFJCZ.js → chunk-RFM27BKZ.js} +31 -7
- package/dist/chunk-RFM27BKZ.js.map +1 -0
- package/dist/{chunk-MYDKIJRH.js → chunk-SVMYFYIA.js} +2 -2
- package/dist/{chunk-MYDKIJRH.js.map → chunk-SVMYFYIA.js.map} +1 -1
- package/dist/{chunk-H3D7BAOL.js → chunk-VOTBNOWH.js} +21 -3
- package/dist/chunk-VOTBNOWH.js.map +1 -0
- package/dist/{chunk-PXVDVUFX.js → chunk-W74FGUZH.js} +10 -3
- package/dist/chunk-W74FGUZH.js.map +1 -0
- package/dist/{chunk-2IWNUDSZ.js → chunk-WNXI3MPM.js} +2 -2
- package/dist/chunk-WNXI3MPM.js.map +1 -0
- package/dist/chunk-XXNCFWXT.js +2118 -0
- package/dist/chunk-XXNCFWXT.js.map +1 -0
- package/dist/{client-bsiNPHBi.d.ts → client-CwOPPpFF.d.ts} +11 -1
- package/dist/{createCartHandler-C4RrPwSG.d.ts → createCartHandler-7DdKB-GF.d.ts} +1 -1
- package/dist/custom-css-Bl7Ur3-H.d.ts +6 -0
- package/dist/editor.d.ts +57 -104
- package/dist/editor.js +270 -1
- package/dist/editor.js.map +1 -1
- package/dist/events-react.d.ts +1 -1
- package/dist/events-react.js +1 -1
- package/dist/events.d.ts +3 -3
- package/dist/events.js +1 -1
- package/dist/{fetch-M3Y-fQrW.d.ts → fetch-Bq_cc8_G.d.ts} +5 -0
- package/dist/hydrogen-Bu7k0kE4.d.ts +259 -0
- package/dist/index.d.ts +74 -51
- package/dist/index.js +19 -15
- package/dist/index.js.map +1 -1
- package/dist/react-router.d.ts +30 -9
- package/dist/react-router.js +16 -7
- package/dist/react-router.js.map +1 -1
- package/dist/react.d.ts +79 -11
- package/dist/react.js +279 -10
- package/dist/react.js.map +1 -1
- package/dist/{routing-sEonP93J.d.ts → routing-Bz3Cg0BW.d.ts} +36 -6
- package/dist/seo.d.ts +7 -4
- package/dist/seo.js +2 -2
- package/dist/settings.d.ts +14 -4
- package/dist/settings.js +2 -2
- package/dist/{shop-BJSvtmnV.d.ts → shop-CnrnSAJw.d.ts} +33 -6
- package/dist/{store-CtCWHI8H.d.ts → store-DO08fzJi.d.ts} +1 -1
- package/dist/storefront-api-types.d.ts +12 -0
- package/dist/{types-BDhHWZsP.d.ts → types-BkY0yET-.d.ts} +3 -3
- package/dist/{types-bcbQzx87.d.ts → types-DZX8ScsJ.d.ts} +2 -0
- package/package.json +4 -4
- package/dist/cart-documents-CwwRTvm8.d.ts +0 -21
- package/dist/chunk-2IWNUDSZ.js.map +0 -1
- package/dist/chunk-4TEFGD42.js.map +0 -1
- package/dist/chunk-7MTCGXIG.js.map +0 -1
- package/dist/chunk-H3D7BAOL.js.map +0 -1
- package/dist/chunk-M7BPQ54D.js.map +0 -1
- package/dist/chunk-PXVDVUFX.js.map +0 -1
- package/dist/chunk-PXZ737N3.js +0 -439
- package/dist/chunk-PXZ737N3.js.map +0 -1
- package/dist/chunk-XMTWFJCZ.js.map +0 -1
|
@@ -0,0 +1,2118 @@
|
|
|
1
|
+
import { HydrogenEditorManifestSchema, HYDROGEN_EDITOR_MANIFEST_LIMITS, HydrogenInstanceSchema, HydrogenInstanceStylesSchema, HydrogenContentBindingSchema, HydrogenDocumentSchema, HydrogenBreakpointSchema, HydrogenComponentDefinitionSchema, HydrogenEditCapabilitySchema, HydrogenSourceLocationSchema, HydrogenUnsupportedFindingSchema } from '@kotao/storefront-contracts';
|
|
2
|
+
|
|
3
|
+
// src/editor/hydrogen.ts
|
|
4
|
+
var HOST_SOURCE = "kotao:workspaces-hydrogen-editor";
|
|
5
|
+
var PREVIEW_SOURCE = "kotao:storefront-hydrogen-preview";
|
|
6
|
+
var SHA_1 = /^[a-f0-9]{40}$/;
|
|
7
|
+
var SHA_256 = /^[a-f0-9]{64}$/;
|
|
8
|
+
var OPAQUE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
9
|
+
var CHANNEL_NONCE = /^[A-Za-z0-9_-]{32,256}$/;
|
|
10
|
+
var OPERATION_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
11
|
+
"CAPABILITY_DENIED",
|
|
12
|
+
"COMPONENT_NOT_FOUND",
|
|
13
|
+
"DOCUMENT_MISMATCH",
|
|
14
|
+
"DUPLICATE_OPERATION_CONFLICT",
|
|
15
|
+
"INSTANCE_NOT_FOUND",
|
|
16
|
+
"INVALID_OPERATION",
|
|
17
|
+
"OPERATION_LIMIT_EXCEEDED",
|
|
18
|
+
"REVISION_MISMATCH",
|
|
19
|
+
"SLOT_CONSTRAINT",
|
|
20
|
+
"UNSUPPORTED_BOUNDARY"
|
|
21
|
+
]);
|
|
22
|
+
var PROTOCOL_FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
23
|
+
"AUTH_CONTEXT_MISMATCH",
|
|
24
|
+
"BUFFER_LIMIT_EXCEEDED",
|
|
25
|
+
"DUPLICATE_MESSAGE_CONFLICT",
|
|
26
|
+
"MESSAGE_EXPIRED",
|
|
27
|
+
"MESSAGE_TOO_DEEP",
|
|
28
|
+
"MESSAGE_TOO_LARGE",
|
|
29
|
+
"SEQUENCE_STALE",
|
|
30
|
+
"UNKNOWN_MESSAGE",
|
|
31
|
+
"UNSUPPORTED_PROTOCOL"
|
|
32
|
+
]);
|
|
33
|
+
var HYDROGEN_EDITOR_PROTOCOL_VERSION = 1;
|
|
34
|
+
var HYDROGEN_EDITOR_BRIDGE_LIMITS = {
|
|
35
|
+
maxMessageBytes: 131072,
|
|
36
|
+
maxMessageDepth: 32,
|
|
37
|
+
maxBufferedMessages: 100,
|
|
38
|
+
maxDraftOperations: 1e3,
|
|
39
|
+
maxProcessedMessages: 2048
|
|
40
|
+
};
|
|
41
|
+
var HydrogenEditorConfigurationError = class extends Error {
|
|
42
|
+
constructor(message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "HydrogenEditorConfigurationError";
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function isRecord(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function hasOnlyKeys(value, keys) {
|
|
51
|
+
const allowed = new Set(keys);
|
|
52
|
+
return Object.keys(value).every((key) => allowed.has(key));
|
|
53
|
+
}
|
|
54
|
+
function isOpaqueId(value) {
|
|
55
|
+
return typeof value === "string" && OPAQUE_ID.test(value);
|
|
56
|
+
}
|
|
57
|
+
function isNonnegativeInteger(value) {
|
|
58
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
59
|
+
}
|
|
60
|
+
function isPositiveInteger(value) {
|
|
61
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
62
|
+
}
|
|
63
|
+
function exactOrigin(value, label) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(value);
|
|
66
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username !== "" || parsed.password !== "" || parsed.origin !== value || parsed.pathname !== "/" || parsed.search !== "" || parsed.hash !== "") {
|
|
67
|
+
throw new Error("not an exact origin");
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
} catch {
|
|
71
|
+
throw new HydrogenEditorConfigurationError(`${label} must be an exact http(s) origin`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function validateSecurityContext(security, now) {
|
|
75
|
+
if (!isOpaqueId(security.sessionId)) {
|
|
76
|
+
throw new HydrogenEditorConfigurationError("sessionId must be a bounded opaque identifier");
|
|
77
|
+
}
|
|
78
|
+
if (!SHA_1.test(security.baseSha)) {
|
|
79
|
+
throw new HydrogenEditorConfigurationError("baseSha must be a 40-character lowercase Git SHA");
|
|
80
|
+
}
|
|
81
|
+
if (!SHA_256.test(security.manifestHash)) {
|
|
82
|
+
throw new HydrogenEditorConfigurationError("manifestHash must be a lowercase SHA-256 digest");
|
|
83
|
+
}
|
|
84
|
+
if (!CHANNEL_NONCE.test(security.channelNonce)) {
|
|
85
|
+
throw new HydrogenEditorConfigurationError(
|
|
86
|
+
"channelNonce must be a 32..256 character base64url value"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (security.previewToken.length < 32 || security.previewToken.length > 4096) {
|
|
90
|
+
throw new HydrogenEditorConfigurationError("previewToken must contain 32..4096 characters");
|
|
91
|
+
}
|
|
92
|
+
if (!Number.isSafeInteger(security.expiresAt) || security.expiresAt <= now) {
|
|
93
|
+
throw new HydrogenEditorConfigurationError("editor preview security context is expired");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function jsonSignature(value) {
|
|
97
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
98
|
+
const visit = (candidate, depth) => {
|
|
99
|
+
if (depth > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageDepth) return false;
|
|
100
|
+
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean" || typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (typeof candidate !== "object") return false;
|
|
104
|
+
if (ancestors.has(candidate)) return false;
|
|
105
|
+
ancestors.add(candidate);
|
|
106
|
+
let valid;
|
|
107
|
+
if (Array.isArray(candidate)) {
|
|
108
|
+
valid = candidate.every((item) => visit(item, depth + 1));
|
|
109
|
+
} else {
|
|
110
|
+
const record = candidate;
|
|
111
|
+
valid = Object.values(record).every((item) => visit(item, depth + 1));
|
|
112
|
+
}
|
|
113
|
+
ancestors.delete(candidate);
|
|
114
|
+
return valid;
|
|
115
|
+
};
|
|
116
|
+
if (!visit(value, 0)) return null;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function protocolFailure(code, message) {
|
|
124
|
+
return { code, message };
|
|
125
|
+
}
|
|
126
|
+
function parseProtocolFailure(value) {
|
|
127
|
+
return isRecord(value) && typeof value.code === "string" && PROTOCOL_FAILURE_CODES.has(value.code) && typeof value.message === "string" && value.message.length <= 2048 ? value : null;
|
|
128
|
+
}
|
|
129
|
+
function operationFailure(code, message, instance, capability) {
|
|
130
|
+
return {
|
|
131
|
+
code,
|
|
132
|
+
message,
|
|
133
|
+
...instance !== void 0 ? { instanceId: instance.instanceId, source: instance.source } : {},
|
|
134
|
+
...capability !== void 0 ? { capability } : {}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function rejectOperation(operation, revision, failure) {
|
|
138
|
+
return {
|
|
139
|
+
status: "rejected",
|
|
140
|
+
revision,
|
|
141
|
+
operationId: operation.operationId,
|
|
142
|
+
failure
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function parseOperation(value) {
|
|
146
|
+
if (!isRecord(value) || !isOpaqueId(value.operationId) || !isPositiveInteger(value.revision) || typeof value.type !== "string") {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const base = {
|
|
150
|
+
operationId: value.operationId,
|
|
151
|
+
revision: value.revision
|
|
152
|
+
};
|
|
153
|
+
switch (value.type) {
|
|
154
|
+
case "prop.patch": {
|
|
155
|
+
if (!hasOnlyKeys(value, [
|
|
156
|
+
"type",
|
|
157
|
+
"operationId",
|
|
158
|
+
"revision",
|
|
159
|
+
"instanceId",
|
|
160
|
+
"propName",
|
|
161
|
+
"binding"
|
|
162
|
+
]) || !isOpaqueId(value.instanceId) || typeof value.propName !== "string" || value.propName.length === 0 || value.propName.length > 128) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const binding = HydrogenContentBindingSchema.safeParse(value.binding);
|
|
166
|
+
return binding.success ? {
|
|
167
|
+
...base,
|
|
168
|
+
type: "prop.patch",
|
|
169
|
+
instanceId: value.instanceId,
|
|
170
|
+
propName: value.propName,
|
|
171
|
+
binding: binding.data
|
|
172
|
+
} : null;
|
|
173
|
+
}
|
|
174
|
+
case "styles.patch": {
|
|
175
|
+
if (!hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId", "styles"]) || !isOpaqueId(value.instanceId)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const styles = HydrogenInstanceStylesSchema.safeParse(value.styles);
|
|
179
|
+
return styles.success ? {
|
|
180
|
+
...base,
|
|
181
|
+
type: "styles.patch",
|
|
182
|
+
instanceId: value.instanceId,
|
|
183
|
+
styles: styles.data
|
|
184
|
+
} : null;
|
|
185
|
+
}
|
|
186
|
+
case "variant.set":
|
|
187
|
+
return hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId", "variantId"]) && isOpaqueId(value.instanceId) && (value.variantId === null || isOpaqueId(value.variantId)) ? {
|
|
188
|
+
...base,
|
|
189
|
+
type: "variant.set",
|
|
190
|
+
instanceId: value.instanceId,
|
|
191
|
+
variantId: value.variantId
|
|
192
|
+
} : null;
|
|
193
|
+
case "slot.insert": {
|
|
194
|
+
if (!hasOnlyKeys(value, [
|
|
195
|
+
"type",
|
|
196
|
+
"operationId",
|
|
197
|
+
"revision",
|
|
198
|
+
"parentInstanceId",
|
|
199
|
+
"slotId",
|
|
200
|
+
"index",
|
|
201
|
+
"instance"
|
|
202
|
+
]) || !isOpaqueId(value.parentInstanceId) || !isOpaqueId(value.slotId) || !isNonnegativeInteger(value.index) || !isRecord(value.instance)) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const parsed = HydrogenInstanceSchema.safeParse({ ...value.instance, parent: null });
|
|
206
|
+
if (!parsed.success) return null;
|
|
207
|
+
const instance = {
|
|
208
|
+
instanceId: parsed.data.instanceId,
|
|
209
|
+
componentId: parsed.data.componentId,
|
|
210
|
+
documentId: parsed.data.documentId,
|
|
211
|
+
source: parsed.data.source,
|
|
212
|
+
...parsed.data.variantId !== void 0 ? { variantId: parsed.data.variantId } : {},
|
|
213
|
+
props: parsed.data.props,
|
|
214
|
+
styles: parsed.data.styles
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
...base,
|
|
218
|
+
type: "slot.insert",
|
|
219
|
+
parentInstanceId: value.parentInstanceId,
|
|
220
|
+
slotId: value.slotId,
|
|
221
|
+
index: value.index,
|
|
222
|
+
instance
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
case "instance.move":
|
|
226
|
+
return hasOnlyKeys(value, [
|
|
227
|
+
"type",
|
|
228
|
+
"operationId",
|
|
229
|
+
"revision",
|
|
230
|
+
"instanceId",
|
|
231
|
+
"parentInstanceId",
|
|
232
|
+
"slotId",
|
|
233
|
+
"index"
|
|
234
|
+
]) && isOpaqueId(value.instanceId) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
|
|
235
|
+
...base,
|
|
236
|
+
type: "instance.move",
|
|
237
|
+
instanceId: value.instanceId,
|
|
238
|
+
parentInstanceId: value.parentInstanceId,
|
|
239
|
+
slotId: value.slotId,
|
|
240
|
+
index: value.index
|
|
241
|
+
} : null;
|
|
242
|
+
case "instance.remove":
|
|
243
|
+
return hasOnlyKeys(value, ["type", "operationId", "revision", "instanceId"]) && isOpaqueId(value.instanceId) ? {
|
|
244
|
+
...base,
|
|
245
|
+
type: "instance.remove",
|
|
246
|
+
instanceId: value.instanceId
|
|
247
|
+
} : null;
|
|
248
|
+
default:
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
var DIRECT_CANVAS_LAYOUT_CONTROLS = /* @__PURE__ */ new Set(["gap", "height", "margin", "padding", "width"]);
|
|
253
|
+
function parseHydrogenEditorCanvasIntent(value) {
|
|
254
|
+
if (!isRecord(value) || typeof value.type !== "string") return null;
|
|
255
|
+
switch (value.type) {
|
|
256
|
+
case "instance.move":
|
|
257
|
+
return hasOnlyKeys(value, ["type", "instanceId", "parentInstanceId", "slotId", "index"]) && isOpaqueId(value.instanceId) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
|
|
258
|
+
type: "instance.move",
|
|
259
|
+
instanceId: value.instanceId,
|
|
260
|
+
parentInstanceId: value.parentInstanceId,
|
|
261
|
+
slotId: value.slotId,
|
|
262
|
+
index: value.index
|
|
263
|
+
} : null;
|
|
264
|
+
case "prop.inline":
|
|
265
|
+
return hasOnlyKeys(value, ["type", "instanceId", "propName", "value"]) && isOpaqueId(value.instanceId) && typeof value.propName === "string" && value.propName.length > 0 && value.propName.length <= 128 && typeof value.value === "string" && value.value.length <= 1e4 ? {
|
|
266
|
+
type: "prop.inline",
|
|
267
|
+
instanceId: value.instanceId,
|
|
268
|
+
propName: value.propName,
|
|
269
|
+
value: value.value
|
|
270
|
+
} : null;
|
|
271
|
+
case "styles.adjust":
|
|
272
|
+
return hasOnlyKeys(value, ["type", "instanceId", "controlId", "value", "breakpointId"]) && isOpaqueId(value.instanceId) && isOpaqueId(value.controlId) && typeof value.value === "number" && Number.isFinite(value.value) && value.value >= 0 && value.value <= 4096 && (value.breakpointId === void 0 || isOpaqueId(value.breakpointId)) ? {
|
|
273
|
+
type: "styles.adjust",
|
|
274
|
+
instanceId: value.instanceId,
|
|
275
|
+
controlId: value.controlId,
|
|
276
|
+
value: Math.round(value.value * 100) / 100,
|
|
277
|
+
...value.breakpointId !== void 0 ? { breakpointId: value.breakpointId } : {}
|
|
278
|
+
} : null;
|
|
279
|
+
case "slot.insert.request":
|
|
280
|
+
return hasOnlyKeys(value, ["type", "parentInstanceId", "slotId", "index"]) && isOpaqueId(value.parentInstanceId) && isOpaqueId(value.slotId) && isNonnegativeInteger(value.index) ? {
|
|
281
|
+
type: "slot.insert.request",
|
|
282
|
+
parentInstanceId: value.parentInstanceId,
|
|
283
|
+
slotId: value.slotId,
|
|
284
|
+
index: value.index
|
|
285
|
+
} : null;
|
|
286
|
+
default:
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function parseEnvelope(value, source, security, state, now) {
|
|
291
|
+
const signature = jsonSignature(value);
|
|
292
|
+
if (signature === null) {
|
|
293
|
+
return protocolFailure("MESSAGE_TOO_DEEP", "message is cyclic, non-JSON, or exceeds depth 32");
|
|
294
|
+
}
|
|
295
|
+
if (new TextEncoder().encode(signature).byteLength > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageBytes) {
|
|
296
|
+
return protocolFailure("MESSAGE_TOO_LARGE", "message exceeds 128 KiB");
|
|
297
|
+
}
|
|
298
|
+
if (!isRecord(value)) {
|
|
299
|
+
return protocolFailure("UNKNOWN_MESSAGE", "message must be an object");
|
|
300
|
+
}
|
|
301
|
+
if (value.protocolVersion !== HYDROGEN_EDITOR_PROTOCOL_VERSION) {
|
|
302
|
+
return protocolFailure("UNSUPPORTED_PROTOCOL", "unsupported Hydrogen editor protocol version");
|
|
303
|
+
}
|
|
304
|
+
if (value.source !== source || value.sessionId !== security.sessionId || value.baseSha !== security.baseSha || value.manifestHash !== security.manifestHash || value.channelNonce !== security.channelNonce || value.previewToken !== security.previewToken) {
|
|
305
|
+
return protocolFailure(
|
|
306
|
+
"AUTH_CONTEXT_MISMATCH",
|
|
307
|
+
"message does not match the active editor session"
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (now >= security.expiresAt) {
|
|
311
|
+
return protocolFailure("MESSAGE_EXPIRED", "editor preview token has expired");
|
|
312
|
+
}
|
|
313
|
+
if (!isOpaqueId(value.messageId) || !isPositiveInteger(value.sequence) || typeof value.type !== "string" || value.type.length > 128) {
|
|
314
|
+
return protocolFailure("UNKNOWN_MESSAGE", "message envelope is invalid");
|
|
315
|
+
}
|
|
316
|
+
const previous = state.messages.get(value.messageId);
|
|
317
|
+
if (previous !== void 0) {
|
|
318
|
+
return previous === signature ? { envelope: value, duplicate: true } : protocolFailure(
|
|
319
|
+
"DUPLICATE_MESSAGE_CONFLICT",
|
|
320
|
+
"messageId was reused with a different payload"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (value.sequence <= state.sequence) {
|
|
324
|
+
return protocolFailure("SEQUENCE_STALE", "message sequence is not newer than the last message");
|
|
325
|
+
}
|
|
326
|
+
state.sequence = value.sequence;
|
|
327
|
+
state.messages.set(value.messageId, signature);
|
|
328
|
+
if (state.messages.size > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxProcessedMessages) {
|
|
329
|
+
const oldest = state.messages.keys().next().value;
|
|
330
|
+
if (typeof oldest === "string") state.messages.delete(oldest);
|
|
331
|
+
}
|
|
332
|
+
return { envelope: value, duplicate: false };
|
|
333
|
+
}
|
|
334
|
+
function createEnvelope(source, security, sequence, type, payload) {
|
|
335
|
+
return {
|
|
336
|
+
source,
|
|
337
|
+
protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
|
|
338
|
+
sessionId: security.sessionId,
|
|
339
|
+
baseSha: security.baseSha,
|
|
340
|
+
manifestHash: security.manifestHash,
|
|
341
|
+
channelNonce: security.channelNonce,
|
|
342
|
+
previewToken: security.previewToken,
|
|
343
|
+
messageId: `${source === HOST_SOURCE ? "host" : "preview"}:${sequence}`,
|
|
344
|
+
sequence,
|
|
345
|
+
type,
|
|
346
|
+
...payload !== void 0 ? { payload } : {}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function postEnvelope(target, targetOrigin, envelope) {
|
|
350
|
+
const signature = jsonSignature(envelope);
|
|
351
|
+
if (signature === null || new TextEncoder().encode(signature).byteLength > HYDROGEN_EDITOR_BRIDGE_LIMITS.maxMessageBytes) {
|
|
352
|
+
throw new HydrogenEditorConfigurationError("outbound editor message exceeds protocol bounds");
|
|
353
|
+
}
|
|
354
|
+
target.postMessage(envelope, targetOrigin);
|
|
355
|
+
}
|
|
356
|
+
function findingsForInstance(manifest, instance, component) {
|
|
357
|
+
const anchors = /* @__PURE__ */ new Set([instance.source.anchorId, component.source.anchorId]);
|
|
358
|
+
return manifest.unsupportedFindings.filter((finding) => {
|
|
359
|
+
switch (finding.target.kind) {
|
|
360
|
+
case "manifest":
|
|
361
|
+
return true;
|
|
362
|
+
case "component":
|
|
363
|
+
return finding.target.componentId === component.componentId;
|
|
364
|
+
case "instance":
|
|
365
|
+
return finding.target.instanceId === instance.instanceId;
|
|
366
|
+
case "source":
|
|
367
|
+
return anchors.has(finding.target.anchorId);
|
|
368
|
+
}
|
|
369
|
+
}).map((finding) => ({
|
|
370
|
+
code: finding.code,
|
|
371
|
+
severity: finding.severity,
|
|
372
|
+
message: finding.message,
|
|
373
|
+
...finding.capability !== void 0 ? { capability: finding.capability } : {},
|
|
374
|
+
...finding.source !== void 0 ? { source: finding.source } : {}
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
function instanceIdsForDocument(instances, documentId) {
|
|
378
|
+
return [...instances.values()].filter((instance) => instance.documentId === documentId).sort((left, right) => left.instanceId.localeCompare(right.instanceId)).map((instance) => instance.instanceId);
|
|
379
|
+
}
|
|
380
|
+
function childrenForSlot(instances, parentInstanceId, slotId) {
|
|
381
|
+
return [...instances.values()].filter(
|
|
382
|
+
(instance) => instance.parent?.instanceId === parentInstanceId && instance.parent.slotId === slotId
|
|
383
|
+
).sort((left, right) => (left.parent?.index ?? 0) - (right.parent?.index ?? 0));
|
|
384
|
+
}
|
|
385
|
+
function resolvedProps(instance, component) {
|
|
386
|
+
const variant = component.variants.find((candidate) => candidate.variantId === instance.variantId);
|
|
387
|
+
const defaults = Object.fromEntries(
|
|
388
|
+
component.props.flatMap(
|
|
389
|
+
(control) => control.defaultBinding === void 0 ? [] : [[control.propName, control.defaultBinding]]
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
return Object.fromEntries(
|
|
393
|
+
Object.entries({ ...defaults, ...instance.props, ...variant?.props }).flatMap(
|
|
394
|
+
([propName, binding]) => binding.kind === "inline" ? [[propName, binding.value]] : []
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
function bindingMatchesControl(binding, control) {
|
|
399
|
+
if (!control.allowedBindings.includes(binding.kind)) return false;
|
|
400
|
+
if (binding.kind === "external") return true;
|
|
401
|
+
const value = binding.value;
|
|
402
|
+
switch (control.control) {
|
|
403
|
+
case "number":
|
|
404
|
+
return typeof value === "number" && Number.isFinite(value) && (control.min === void 0 || value >= control.min) && (control.max === void 0 || value <= control.max);
|
|
405
|
+
case "boolean":
|
|
406
|
+
return typeof value === "boolean";
|
|
407
|
+
case "select":
|
|
408
|
+
return typeof value === "string" && control.options.some((option) => option.value === value);
|
|
409
|
+
case "text":
|
|
410
|
+
case "textarea":
|
|
411
|
+
case "richtext":
|
|
412
|
+
case "color":
|
|
413
|
+
case "image":
|
|
414
|
+
case "url":
|
|
415
|
+
case "product":
|
|
416
|
+
case "collection":
|
|
417
|
+
return typeof value === "string";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function styleValueAllowed(value, allowedValues) {
|
|
421
|
+
return (typeof value === "string" || typeof value === "number" && Number.isFinite(value)) && (allowedValues === void 0 || allowedValues.includes(String(value)));
|
|
422
|
+
}
|
|
423
|
+
function instanceMatchesComponentContract(manifest, instance, component) {
|
|
424
|
+
const controls = new Map(component.props.map((control) => [control.propName, control]));
|
|
425
|
+
for (const [propName, binding] of Object.entries(instance.props)) {
|
|
426
|
+
const control = controls.get(propName);
|
|
427
|
+
if (control === void 0 || !bindingMatchesControl(binding, control)) return false;
|
|
428
|
+
}
|
|
429
|
+
if (component.props.some(
|
|
430
|
+
(control) => control.required && instance.props[control.propName] === void 0 && control.defaultBinding === void 0
|
|
431
|
+
)) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
if (instance.variantId !== void 0 && !component.variants.some((variant) => variant.variantId === instance.variantId)) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
const tokenControls = new Map(component.styles.tokens.map((token) => [token.tokenId, token]));
|
|
438
|
+
const layoutControls = new Map(
|
|
439
|
+
component.styles.layout.map((control) => [control.controlId, control])
|
|
440
|
+
);
|
|
441
|
+
const breakpoints = new Set(manifest.breakpoints.map((breakpoint) => breakpoint.breakpointId));
|
|
442
|
+
const overrideIds = /* @__PURE__ */ new Set();
|
|
443
|
+
for (const override of instance.styles.overrides) {
|
|
444
|
+
if (!component.styles.responsive || !breakpoints.has(override.breakpointId) || overrideIds.has(override.breakpointId)) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
overrideIds.add(override.breakpointId);
|
|
448
|
+
}
|
|
449
|
+
for (const values of [
|
|
450
|
+
instance.styles.base,
|
|
451
|
+
...instance.styles.overrides.map((override) => override.values)
|
|
452
|
+
]) {
|
|
453
|
+
for (const [tokenId, value] of Object.entries(values.tokens)) {
|
|
454
|
+
const control = tokenControls.get(tokenId);
|
|
455
|
+
if (control === void 0 || !styleValueAllowed(value, control.allowedValues)) return false;
|
|
456
|
+
}
|
|
457
|
+
for (const [controlId, value] of Object.entries(values.layout)) {
|
|
458
|
+
const control = layoutControls.get(controlId);
|
|
459
|
+
if (control === void 0 || !styleValueAllowed(value, control.allowedValues)) return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
function cloneManifest(manifest) {
|
|
465
|
+
return HydrogenEditorManifestSchema.parse(manifest);
|
|
466
|
+
}
|
|
467
|
+
var HydrogenEditorPreviewStoreImpl = class {
|
|
468
|
+
manifest;
|
|
469
|
+
components;
|
|
470
|
+
documents;
|
|
471
|
+
instances = /* @__PURE__ */ new Map();
|
|
472
|
+
mounted = /* @__PURE__ */ new Set();
|
|
473
|
+
operations = /* @__PURE__ */ new Map();
|
|
474
|
+
listeners = /* @__PURE__ */ new Set();
|
|
475
|
+
instanceListeners = /* @__PURE__ */ new Map();
|
|
476
|
+
snapshotCache = null;
|
|
477
|
+
instanceSnapshotCache = /* @__PURE__ */ new Map();
|
|
478
|
+
documentId;
|
|
479
|
+
route;
|
|
480
|
+
revision = 0;
|
|
481
|
+
selectedInstanceId = null;
|
|
482
|
+
hoveredInstanceId = null;
|
|
483
|
+
constructor(manifest, documentId, route) {
|
|
484
|
+
this.manifest = cloneManifest(manifest);
|
|
485
|
+
this.components = new Map(
|
|
486
|
+
this.manifest.components.map((component) => [component.componentId, component])
|
|
487
|
+
);
|
|
488
|
+
this.documents = new Map(
|
|
489
|
+
this.manifest.documents.map((document) => [document.documentId, document])
|
|
490
|
+
);
|
|
491
|
+
for (const instance of this.manifest.instances)
|
|
492
|
+
this.instances.set(instance.instanceId, instance);
|
|
493
|
+
if (!this.documents.has(documentId)) {
|
|
494
|
+
throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
|
|
495
|
+
}
|
|
496
|
+
this.documentId = documentId;
|
|
497
|
+
this.route = route;
|
|
498
|
+
}
|
|
499
|
+
getSnapshot() {
|
|
500
|
+
if (this.snapshotCache !== null) return this.snapshotCache;
|
|
501
|
+
const document = this.documents.get(this.documentId);
|
|
502
|
+
if (document === void 0) {
|
|
503
|
+
throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${this.documentId}"`);
|
|
504
|
+
}
|
|
505
|
+
const instanceIds = instanceIdsForDocument(this.instances, this.documentId);
|
|
506
|
+
const selected = this.selectedInstanceId;
|
|
507
|
+
const selectedInstance = selected === null ? void 0 : this.instances.get(selected);
|
|
508
|
+
this.snapshotCache = {
|
|
509
|
+
document,
|
|
510
|
+
...this.route !== void 0 ? { route: this.route } : {},
|
|
511
|
+
revision: this.revision,
|
|
512
|
+
selectedInstanceId: selected,
|
|
513
|
+
hoveredInstanceId: this.hoveredInstanceId,
|
|
514
|
+
selectionStatus: selected === null ? "none" : selectedInstance?.documentId === this.documentId ? "active" : "out-of-route",
|
|
515
|
+
instanceIds,
|
|
516
|
+
mountedInstanceIds: instanceIds.filter((instanceId) => this.mounted.has(instanceId))
|
|
517
|
+
};
|
|
518
|
+
return this.snapshotCache;
|
|
519
|
+
}
|
|
520
|
+
getInstanceSnapshot(instanceId) {
|
|
521
|
+
if (this.instanceSnapshotCache.has(instanceId)) {
|
|
522
|
+
return this.instanceSnapshotCache.get(instanceId) ?? null;
|
|
523
|
+
}
|
|
524
|
+
const instance = this.instances.get(instanceId);
|
|
525
|
+
const component = instance === void 0 ? void 0 : this.components.get(instance.componentId);
|
|
526
|
+
if (instance === void 0 || component === void 0) {
|
|
527
|
+
this.instanceSnapshotCache.set(instanceId, null);
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
const childInstanceIds = Object.fromEntries(
|
|
531
|
+
component.slots.map((slot) => [
|
|
532
|
+
slot.slotId,
|
|
533
|
+
childrenForSlot(this.instances, instanceId, slot.slotId).map((child) => child.instanceId)
|
|
534
|
+
])
|
|
535
|
+
);
|
|
536
|
+
const capabilityReasons = findingsForInstance(this.manifest, instance, component);
|
|
537
|
+
const snapshot = {
|
|
538
|
+
instance,
|
|
539
|
+
component,
|
|
540
|
+
childInstanceIds,
|
|
541
|
+
resolvedProps: resolvedProps(instance, component),
|
|
542
|
+
mounted: this.mounted.has(instanceId),
|
|
543
|
+
editable: component.capabilities.length > 0 && !capabilityReasons.some((reason) => reason.severity === "blocking"),
|
|
544
|
+
capabilityReasons
|
|
545
|
+
};
|
|
546
|
+
this.instanceSnapshotCache.set(instanceId, snapshot);
|
|
547
|
+
return snapshot;
|
|
548
|
+
}
|
|
549
|
+
subscribe(listener) {
|
|
550
|
+
this.listeners.add(listener);
|
|
551
|
+
return () => this.listeners.delete(listener);
|
|
552
|
+
}
|
|
553
|
+
subscribeInstance(instanceId, listener) {
|
|
554
|
+
const listeners = this.instanceListeners.get(instanceId) ?? /* @__PURE__ */ new Set();
|
|
555
|
+
listeners.add(listener);
|
|
556
|
+
this.instanceListeners.set(instanceId, listeners);
|
|
557
|
+
return () => {
|
|
558
|
+
listeners.delete(listener);
|
|
559
|
+
if (listeners.size === 0) this.instanceListeners.delete(instanceId);
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
setMounted(instanceId, mounted) {
|
|
563
|
+
if (!this.instances.has(instanceId)) return;
|
|
564
|
+
const changed = mounted ? !this.mounted.has(instanceId) : this.mounted.has(instanceId);
|
|
565
|
+
if (!changed) return;
|
|
566
|
+
if (mounted) this.mounted.add(instanceId);
|
|
567
|
+
else this.mounted.delete(instanceId);
|
|
568
|
+
this.emit(/* @__PURE__ */ new Set([instanceId]));
|
|
569
|
+
}
|
|
570
|
+
setSelection(instanceId) {
|
|
571
|
+
if (instanceId !== null && !this.instances.has(instanceId)) return false;
|
|
572
|
+
if (this.selectedInstanceId === instanceId) return true;
|
|
573
|
+
this.selectedInstanceId = instanceId;
|
|
574
|
+
this.emit(/* @__PURE__ */ new Set());
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
setHover(instanceId) {
|
|
578
|
+
if (instanceId !== null && !this.instances.has(instanceId)) return false;
|
|
579
|
+
if (this.hoveredInstanceId === instanceId) return true;
|
|
580
|
+
this.hoveredInstanceId = instanceId;
|
|
581
|
+
this.emit(/* @__PURE__ */ new Set());
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
reconcile(documentId, route) {
|
|
585
|
+
if (!this.documents.has(documentId)) {
|
|
586
|
+
throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
|
|
587
|
+
}
|
|
588
|
+
this.documentId = documentId;
|
|
589
|
+
this.route = route;
|
|
590
|
+
if (this.hoveredInstanceId !== null && this.instances.get(this.hoveredInstanceId)?.documentId !== documentId) {
|
|
591
|
+
this.hoveredInstanceId = null;
|
|
592
|
+
}
|
|
593
|
+
this.emit(/* @__PURE__ */ new Set());
|
|
594
|
+
}
|
|
595
|
+
emit(changedInstanceIds) {
|
|
596
|
+
this.snapshotCache = null;
|
|
597
|
+
for (const instanceId of changedInstanceIds) {
|
|
598
|
+
this.instanceSnapshotCache.delete(instanceId);
|
|
599
|
+
for (const listener of this.instanceListeners.get(instanceId) ?? []) listener();
|
|
600
|
+
}
|
|
601
|
+
for (const listener of this.listeners) listener();
|
|
602
|
+
}
|
|
603
|
+
componentFor(instance) {
|
|
604
|
+
const component = this.components.get(instance.componentId);
|
|
605
|
+
return component ?? operationFailure(
|
|
606
|
+
"COMPONENT_NOT_FOUND",
|
|
607
|
+
`component "${instance.componentId}" is not declared`,
|
|
608
|
+
instance
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
rejectUnsupported(instance, capability) {
|
|
612
|
+
const component = this.components.get(instance.componentId);
|
|
613
|
+
if (component === void 0) {
|
|
614
|
+
return operationFailure(
|
|
615
|
+
"COMPONENT_NOT_FOUND",
|
|
616
|
+
`component "${instance.componentId}" is not declared`,
|
|
617
|
+
instance
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const blocking = findingsForInstance(this.manifest, instance, component).find(
|
|
621
|
+
(finding) => finding.severity === "blocking" && (finding.capability === void 0 || finding.capability === capability)
|
|
622
|
+
);
|
|
623
|
+
return blocking === void 0 ? null : operationFailure(
|
|
624
|
+
"UNSUPPORTED_BOUNDARY",
|
|
625
|
+
blocking.message,
|
|
626
|
+
instance,
|
|
627
|
+
blocking.capability ?? capability
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
requireCapability(instance, capability) {
|
|
631
|
+
const component = this.componentFor(instance);
|
|
632
|
+
if ("code" in component) return component;
|
|
633
|
+
if (!component.capabilities.includes(capability)) {
|
|
634
|
+
return operationFailure(
|
|
635
|
+
"CAPABILITY_DENIED",
|
|
636
|
+
`component "${component.componentId}" does not allow "${capability}" edits`,
|
|
637
|
+
instance,
|
|
638
|
+
capability
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
return this.rejectUnsupported(instance, capability);
|
|
642
|
+
}
|
|
643
|
+
validateStyles(instance, component, styles) {
|
|
644
|
+
const capabilityFailure = this.requireCapability(instance, "styles") ?? (styles.overrides.length > 0 ? this.requireCapability(instance, "responsive") : null);
|
|
645
|
+
if (capabilityFailure !== null) return capabilityFailure;
|
|
646
|
+
const tokenControls = new Map(component.styles.tokens.map((token) => [token.tokenId, token]));
|
|
647
|
+
const layoutControls = new Map(
|
|
648
|
+
component.styles.layout.map((control) => [control.controlId, control])
|
|
649
|
+
);
|
|
650
|
+
const breakpoints = new Set(this.manifest.breakpoints.map((item) => item.breakpointId));
|
|
651
|
+
const overrideIds = /* @__PURE__ */ new Set();
|
|
652
|
+
for (const override of styles.overrides) {
|
|
653
|
+
if (overrideIds.has(override.breakpointId) || !breakpoints.has(override.breakpointId)) {
|
|
654
|
+
return operationFailure(
|
|
655
|
+
"CAPABILITY_DENIED",
|
|
656
|
+
`responsive breakpoint "${override.breakpointId}" is unknown or duplicated`,
|
|
657
|
+
instance,
|
|
658
|
+
"responsive"
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
overrideIds.add(override.breakpointId);
|
|
662
|
+
}
|
|
663
|
+
const values = [styles.base, ...styles.overrides.map((override) => override.values)];
|
|
664
|
+
for (const value of values) {
|
|
665
|
+
for (const [tokenId, tokenValue] of Object.entries(value.tokens)) {
|
|
666
|
+
const control = tokenControls.get(tokenId);
|
|
667
|
+
if (control === void 0 || !styleValueAllowed(tokenValue, control.allowedValues)) {
|
|
668
|
+
return operationFailure(
|
|
669
|
+
"CAPABILITY_DENIED",
|
|
670
|
+
`style token "${tokenId}" is undeclared or outside its allowed values`,
|
|
671
|
+
instance,
|
|
672
|
+
"styles"
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
for (const [controlId, layoutValue] of Object.entries(value.layout)) {
|
|
677
|
+
const control = layoutControls.get(controlId);
|
|
678
|
+
if (control === void 0 || !styleValueAllowed(layoutValue, control.allowedValues)) {
|
|
679
|
+
return operationFailure(
|
|
680
|
+
"CAPABILITY_DENIED",
|
|
681
|
+
`layout control "${controlId}" is undeclared or outside its allowed values`,
|
|
682
|
+
instance,
|
|
683
|
+
"styles"
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
validateInsertedInstance(instance, component) {
|
|
691
|
+
const propControls = new Map(component.props.map((control) => [control.propName, control]));
|
|
692
|
+
for (const [propName, binding] of Object.entries(instance.props)) {
|
|
693
|
+
const control = propControls.get(propName);
|
|
694
|
+
const contentCapability = binding.kind === "external" ? "external-content" : "inline-content";
|
|
695
|
+
const capabilityFailure = this.requireCapability(instance, "props") ?? this.requireCapability(instance, contentCapability);
|
|
696
|
+
if (control === void 0 || !bindingMatchesControl(binding, control) || capabilityFailure !== null) {
|
|
697
|
+
return capabilityFailure ?? operationFailure(
|
|
698
|
+
"CAPABILITY_DENIED",
|
|
699
|
+
`inserted prop "${propName}" is not declared or violates its control`,
|
|
700
|
+
instance,
|
|
701
|
+
contentCapability
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
for (const control of component.props) {
|
|
706
|
+
if (control.required && instance.props[control.propName] === void 0 && control.defaultBinding === void 0) {
|
|
707
|
+
return operationFailure(
|
|
708
|
+
"CAPABILITY_DENIED",
|
|
709
|
+
`inserted instance is missing required prop "${control.propName}"`,
|
|
710
|
+
instance,
|
|
711
|
+
"props"
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
if (instance.variantId !== void 0 && !component.variants.some((variant) => variant.variantId === instance.variantId)) {
|
|
716
|
+
return operationFailure(
|
|
717
|
+
"CAPABILITY_DENIED",
|
|
718
|
+
`variant "${instance.variantId}" is not declared`,
|
|
719
|
+
instance,
|
|
720
|
+
"variants"
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const styleValuesPresent = Object.keys(instance.styles.base.tokens).length > 0 || Object.keys(instance.styles.base.layout).length > 0 || instance.styles.overrides.length > 0;
|
|
724
|
+
if (styleValuesPresent) {
|
|
725
|
+
const styleFailure = this.validateStyles(instance, component, instance.styles);
|
|
726
|
+
if (styleFailure !== null) return styleFailure;
|
|
727
|
+
}
|
|
728
|
+
const sourceAnchorCollision = this.manifest.components.some(
|
|
729
|
+
(candidate) => candidate.source.anchorId === instance.source.anchorId
|
|
730
|
+
) || [...this.instances.values()].some(
|
|
731
|
+
(candidate) => candidate.source.anchorId === instance.source.anchorId
|
|
732
|
+
);
|
|
733
|
+
if (sourceAnchorCollision) {
|
|
734
|
+
return operationFailure(
|
|
735
|
+
"INVALID_OPERATION",
|
|
736
|
+
`source anchor "${instance.source.anchorId}" is already in use`,
|
|
737
|
+
instance,
|
|
738
|
+
"insert"
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
return this.rejectUnsupported(instance, "insert");
|
|
742
|
+
}
|
|
743
|
+
normalizeSlot(parentInstanceId, slotId) {
|
|
744
|
+
const changed = /* @__PURE__ */ new Set([parentInstanceId]);
|
|
745
|
+
for (const [index, child] of childrenForSlot(
|
|
746
|
+
this.instances,
|
|
747
|
+
parentInstanceId,
|
|
748
|
+
slotId
|
|
749
|
+
).entries()) {
|
|
750
|
+
if (child.parent?.index === index) continue;
|
|
751
|
+
this.instances.set(child.instanceId, {
|
|
752
|
+
...child,
|
|
753
|
+
parent: { instanceId: parentInstanceId, slotId, index }
|
|
754
|
+
});
|
|
755
|
+
changed.add(child.instanceId);
|
|
756
|
+
}
|
|
757
|
+
return changed;
|
|
758
|
+
}
|
|
759
|
+
insertAt(instance, parentInstanceId, slotId, index) {
|
|
760
|
+
const siblings = childrenForSlot(this.instances, parentInstanceId, slotId);
|
|
761
|
+
const boundedIndex = Math.min(index, siblings.length);
|
|
762
|
+
for (const sibling of siblings) {
|
|
763
|
+
const siblingIndex = sibling.parent?.index ?? 0;
|
|
764
|
+
if (siblingIndex < boundedIndex) continue;
|
|
765
|
+
this.instances.set(sibling.instanceId, {
|
|
766
|
+
...sibling,
|
|
767
|
+
parent: { instanceId: parentInstanceId, slotId, index: siblingIndex + 1 }
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
this.instances.set(instance.instanceId, {
|
|
771
|
+
...instance,
|
|
772
|
+
parent: { instanceId: parentInstanceId, slotId, index: boundedIndex }
|
|
773
|
+
});
|
|
774
|
+
return /* @__PURE__ */ new Set([
|
|
775
|
+
instance.instanceId,
|
|
776
|
+
parentInstanceId,
|
|
777
|
+
...siblings.map((item) => item.instanceId)
|
|
778
|
+
]);
|
|
779
|
+
}
|
|
780
|
+
validateSlotTarget(parent, slotId, childComponentId, mode) {
|
|
781
|
+
const capability = mode === "insert" ? "insert" : "reorder";
|
|
782
|
+
const capabilityFailure = this.requireCapability(parent, capability);
|
|
783
|
+
if (capabilityFailure !== null) return capabilityFailure;
|
|
784
|
+
const component = this.components.get(parent.componentId);
|
|
785
|
+
const slot = component?.slots.find((candidate) => candidate.slotId === slotId);
|
|
786
|
+
if (slot === void 0) {
|
|
787
|
+
return operationFailure(
|
|
788
|
+
"SLOT_CONSTRAINT",
|
|
789
|
+
`component "${parent.componentId}" has no slot "${slotId}"`,
|
|
790
|
+
parent,
|
|
791
|
+
capability
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (slot.insertion === "fixed" || mode === "reorder" && slot.insertion !== "reorder" || slot.accepts.mode === "include" && !slot.accepts.componentIds.includes(childComponentId)) {
|
|
795
|
+
return operationFailure(
|
|
796
|
+
"SLOT_CONSTRAINT",
|
|
797
|
+
`slot "${slotId}" does not accept this ${mode} operation`,
|
|
798
|
+
parent,
|
|
799
|
+
capability
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
applyValidatedOperation(operation) {
|
|
805
|
+
switch (operation.type) {
|
|
806
|
+
case "prop.patch": {
|
|
807
|
+
const instance = this.instances.get(operation.instanceId);
|
|
808
|
+
if (instance === void 0) {
|
|
809
|
+
return {
|
|
810
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
811
|
+
result: rejectOperation(
|
|
812
|
+
operation,
|
|
813
|
+
this.revision,
|
|
814
|
+
operationFailure(
|
|
815
|
+
"INSTANCE_NOT_FOUND",
|
|
816
|
+
`instance "${operation.instanceId}" is not declared`
|
|
817
|
+
)
|
|
818
|
+
)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
const component = this.components.get(instance.componentId);
|
|
822
|
+
const control = component?.props.find((prop) => prop.propName === operation.propName);
|
|
823
|
+
const requiredCapability = operation.binding.kind === "external" ? "external-content" : "inline-content";
|
|
824
|
+
const capabilityFailure = this.requireCapability(instance, "props") ?? this.requireCapability(instance, requiredCapability);
|
|
825
|
+
if (capabilityFailure !== null || control === void 0 || !bindingMatchesControl(operation.binding, control)) {
|
|
826
|
+
return {
|
|
827
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
828
|
+
result: rejectOperation(
|
|
829
|
+
operation,
|
|
830
|
+
this.revision,
|
|
831
|
+
capabilityFailure ?? operationFailure(
|
|
832
|
+
"CAPABILITY_DENIED",
|
|
833
|
+
`prop "${operation.propName}" does not allow "${operation.binding.kind}" content`,
|
|
834
|
+
instance,
|
|
835
|
+
requiredCapability
|
|
836
|
+
)
|
|
837
|
+
)
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
this.instances.set(instance.instanceId, {
|
|
841
|
+
...instance,
|
|
842
|
+
props: { ...instance.props, [operation.propName]: operation.binding }
|
|
843
|
+
});
|
|
844
|
+
return {
|
|
845
|
+
changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
|
|
846
|
+
result: {
|
|
847
|
+
status: "applied",
|
|
848
|
+
revision: operation.revision,
|
|
849
|
+
operationId: operation.operationId
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
case "styles.patch": {
|
|
854
|
+
const instance = this.instances.get(operation.instanceId);
|
|
855
|
+
if (instance === void 0) {
|
|
856
|
+
return {
|
|
857
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
858
|
+
result: rejectOperation(
|
|
859
|
+
operation,
|
|
860
|
+
this.revision,
|
|
861
|
+
operationFailure(
|
|
862
|
+
"INSTANCE_NOT_FOUND",
|
|
863
|
+
`instance "${operation.instanceId}" is not declared`
|
|
864
|
+
)
|
|
865
|
+
)
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
const component = this.components.get(instance.componentId);
|
|
869
|
+
const capabilityFailure = component === void 0 ? operationFailure(
|
|
870
|
+
"COMPONENT_NOT_FOUND",
|
|
871
|
+
`component "${instance.componentId}" is not declared`,
|
|
872
|
+
instance
|
|
873
|
+
) : this.validateStyles(instance, component, operation.styles);
|
|
874
|
+
if (capabilityFailure !== null) {
|
|
875
|
+
return {
|
|
876
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
877
|
+
result: rejectOperation(operation, this.revision, capabilityFailure)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
this.instances.set(instance.instanceId, { ...instance, styles: operation.styles });
|
|
881
|
+
return {
|
|
882
|
+
changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
|
|
883
|
+
result: {
|
|
884
|
+
status: "applied",
|
|
885
|
+
revision: operation.revision,
|
|
886
|
+
operationId: operation.operationId
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
case "variant.set": {
|
|
891
|
+
const instance = this.instances.get(operation.instanceId);
|
|
892
|
+
if (instance === void 0) {
|
|
893
|
+
return {
|
|
894
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
895
|
+
result: rejectOperation(
|
|
896
|
+
operation,
|
|
897
|
+
this.revision,
|
|
898
|
+
operationFailure(
|
|
899
|
+
"INSTANCE_NOT_FOUND",
|
|
900
|
+
`instance "${operation.instanceId}" is not declared`
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
const capabilityFailure = this.requireCapability(instance, "variants");
|
|
906
|
+
const component = this.components.get(instance.componentId);
|
|
907
|
+
if (capabilityFailure !== null || operation.variantId !== null && !component?.variants.some((variant) => variant.variantId === operation.variantId)) {
|
|
908
|
+
return {
|
|
909
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
910
|
+
result: rejectOperation(
|
|
911
|
+
operation,
|
|
912
|
+
this.revision,
|
|
913
|
+
capabilityFailure ?? operationFailure(
|
|
914
|
+
"CAPABILITY_DENIED",
|
|
915
|
+
`variant "${operation.variantId}" is not declared`,
|
|
916
|
+
instance,
|
|
917
|
+
"variants"
|
|
918
|
+
)
|
|
919
|
+
)
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
const next = operation.variantId === null ? (({ variantId: _variantId, ...rest }) => rest)(instance) : { ...instance, variantId: operation.variantId };
|
|
923
|
+
this.instances.set(instance.instanceId, next);
|
|
924
|
+
return {
|
|
925
|
+
changedInstanceIds: /* @__PURE__ */ new Set([instance.instanceId]),
|
|
926
|
+
result: {
|
|
927
|
+
status: "applied",
|
|
928
|
+
revision: operation.revision,
|
|
929
|
+
operationId: operation.operationId
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
case "slot.insert": {
|
|
934
|
+
const parent = this.instances.get(operation.parentInstanceId);
|
|
935
|
+
const component = this.components.get(operation.instance.componentId);
|
|
936
|
+
if (parent === void 0 || component === void 0) {
|
|
937
|
+
return {
|
|
938
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
939
|
+
result: rejectOperation(
|
|
940
|
+
operation,
|
|
941
|
+
this.revision,
|
|
942
|
+
parent === void 0 ? operationFailure(
|
|
943
|
+
"INSTANCE_NOT_FOUND",
|
|
944
|
+
`parent instance "${operation.parentInstanceId}" is not declared`
|
|
945
|
+
) : operationFailure(
|
|
946
|
+
"COMPONENT_NOT_FOUND",
|
|
947
|
+
`component "${operation.instance.componentId}" is not declared`
|
|
948
|
+
)
|
|
949
|
+
)
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const documentInstanceCount = [...this.instances.values()].filter(
|
|
953
|
+
(instance) => instance.documentId === parent.documentId
|
|
954
|
+
).length;
|
|
955
|
+
let parentDepth = 1;
|
|
956
|
+
let ancestor = parent;
|
|
957
|
+
while (ancestor.parent !== null) {
|
|
958
|
+
parentDepth += 1;
|
|
959
|
+
ancestor = this.instances.get(ancestor.parent.instanceId);
|
|
960
|
+
if (ancestor === void 0) break;
|
|
961
|
+
}
|
|
962
|
+
if (this.instances.has(operation.instance.instanceId) || operation.instance.documentId !== parent.documentId || this.instances.size >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstances || documentInstanceCount >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstancesPerDocument || parentDepth >= HYDROGEN_EDITOR_MANIFEST_LIMITS.maxDepth) {
|
|
963
|
+
return {
|
|
964
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
965
|
+
result: rejectOperation(
|
|
966
|
+
operation,
|
|
967
|
+
this.revision,
|
|
968
|
+
operationFailure(
|
|
969
|
+
"DOCUMENT_MISMATCH",
|
|
970
|
+
"inserted instance identity is duplicate, cross-document, or exceeds manifest limits",
|
|
971
|
+
parent,
|
|
972
|
+
"insert"
|
|
973
|
+
)
|
|
974
|
+
)
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
const targetFailure = this.validateSlotTarget(
|
|
978
|
+
parent,
|
|
979
|
+
operation.slotId,
|
|
980
|
+
operation.instance.componentId,
|
|
981
|
+
"insert"
|
|
982
|
+
);
|
|
983
|
+
const slot = this.components.get(parent.componentId)?.slots.find((candidate) => candidate.slotId === operation.slotId);
|
|
984
|
+
if (targetFailure !== null || slot === void 0 || childrenForSlot(this.instances, parent.instanceId, operation.slotId).length >= slot.maxInstances) {
|
|
985
|
+
return {
|
|
986
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
987
|
+
result: rejectOperation(
|
|
988
|
+
operation,
|
|
989
|
+
this.revision,
|
|
990
|
+
targetFailure ?? operationFailure(
|
|
991
|
+
"SLOT_CONSTRAINT",
|
|
992
|
+
`slot "${operation.slotId}" reached its instance limit`,
|
|
993
|
+
parent,
|
|
994
|
+
"insert"
|
|
995
|
+
)
|
|
996
|
+
)
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
const parsed = HydrogenInstanceSchema.safeParse({
|
|
1000
|
+
...operation.instance,
|
|
1001
|
+
parent: {
|
|
1002
|
+
instanceId: parent.instanceId,
|
|
1003
|
+
slotId: operation.slotId,
|
|
1004
|
+
index: operation.index
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
if (!parsed.success) {
|
|
1008
|
+
return {
|
|
1009
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1010
|
+
result: rejectOperation(
|
|
1011
|
+
operation,
|
|
1012
|
+
this.revision,
|
|
1013
|
+
operationFailure(
|
|
1014
|
+
"INVALID_OPERATION",
|
|
1015
|
+
"inserted instance does not satisfy the Hydrogen instance contract",
|
|
1016
|
+
parent,
|
|
1017
|
+
"insert"
|
|
1018
|
+
)
|
|
1019
|
+
)
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
const insertedFailure = this.validateInsertedInstance(parsed.data, component);
|
|
1023
|
+
if (insertedFailure !== null) {
|
|
1024
|
+
return {
|
|
1025
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1026
|
+
result: rejectOperation(operation, this.revision, insertedFailure)
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
changedInstanceIds: this.insertAt(
|
|
1031
|
+
parsed.data,
|
|
1032
|
+
parent.instanceId,
|
|
1033
|
+
operation.slotId,
|
|
1034
|
+
operation.index
|
|
1035
|
+
),
|
|
1036
|
+
result: {
|
|
1037
|
+
status: "applied",
|
|
1038
|
+
revision: operation.revision,
|
|
1039
|
+
operationId: operation.operationId
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
case "instance.move": {
|
|
1044
|
+
const instance = this.instances.get(operation.instanceId);
|
|
1045
|
+
const parent = this.instances.get(operation.parentInstanceId);
|
|
1046
|
+
if (instance === void 0 || parent === void 0 || instance.parent === null) {
|
|
1047
|
+
return {
|
|
1048
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1049
|
+
result: rejectOperation(
|
|
1050
|
+
operation,
|
|
1051
|
+
this.revision,
|
|
1052
|
+
operationFailure(
|
|
1053
|
+
"INSTANCE_NOT_FOUND",
|
|
1054
|
+
"move requires an existing non-root instance and parent",
|
|
1055
|
+
instance,
|
|
1056
|
+
"reorder"
|
|
1057
|
+
)
|
|
1058
|
+
)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const targetFailure = this.validateSlotTarget(
|
|
1062
|
+
parent,
|
|
1063
|
+
operation.slotId,
|
|
1064
|
+
instance.componentId,
|
|
1065
|
+
"reorder"
|
|
1066
|
+
);
|
|
1067
|
+
const oldParent = this.instances.get(instance.parent.instanceId);
|
|
1068
|
+
const oldSlot = this.components.get(oldParent?.componentId ?? "")?.slots.find((slot) => slot.slotId === instance.parent?.slotId);
|
|
1069
|
+
const oldTargetFailure = oldParent === void 0 || oldSlot === void 0 ? null : this.validateSlotTarget(oldParent, oldSlot.slotId, instance.componentId, "reorder");
|
|
1070
|
+
const targetSlot = this.components.get(parent.componentId)?.slots.find((slot) => slot.slotId === operation.slotId);
|
|
1071
|
+
const movingWithinSlot = instance.parent.instanceId === parent.instanceId && instance.parent.slotId === operation.slotId;
|
|
1072
|
+
const targetCount = childrenForSlot(
|
|
1073
|
+
this.instances,
|
|
1074
|
+
parent.instanceId,
|
|
1075
|
+
operation.slotId
|
|
1076
|
+
).length;
|
|
1077
|
+
let cursor = parent;
|
|
1078
|
+
let createsCycle = false;
|
|
1079
|
+
while (cursor?.parent !== null && cursor?.parent !== void 0) {
|
|
1080
|
+
if (cursor.instanceId === instance.instanceId) {
|
|
1081
|
+
createsCycle = true;
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
cursor = this.instances.get(cursor.parent.instanceId);
|
|
1085
|
+
}
|
|
1086
|
+
if (targetFailure !== null || oldTargetFailure !== null || oldParent === void 0 || oldSlot === void 0 || targetSlot === void 0 || !movingWithinSlot && targetCount >= targetSlot.maxInstances || !movingWithinSlot && childrenForSlot(this.instances, oldParent.instanceId, oldSlot.slotId).length <= oldSlot.minInstances || createsCycle || parent.documentId !== instance.documentId) {
|
|
1087
|
+
return {
|
|
1088
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1089
|
+
result: rejectOperation(
|
|
1090
|
+
operation,
|
|
1091
|
+
this.revision,
|
|
1092
|
+
targetFailure ?? oldTargetFailure ?? operationFailure(
|
|
1093
|
+
"SLOT_CONSTRAINT",
|
|
1094
|
+
"move violates slot cardinality, document, or tree constraints",
|
|
1095
|
+
instance,
|
|
1096
|
+
"reorder"
|
|
1097
|
+
)
|
|
1098
|
+
)
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
this.instances.delete(instance.instanceId);
|
|
1102
|
+
const changed = this.normalizeSlot(oldParent.instanceId, oldSlot.slotId);
|
|
1103
|
+
const inserted = this.insertAt(
|
|
1104
|
+
instance,
|
|
1105
|
+
parent.instanceId,
|
|
1106
|
+
operation.slotId,
|
|
1107
|
+
operation.index
|
|
1108
|
+
);
|
|
1109
|
+
for (const id of inserted) changed.add(id);
|
|
1110
|
+
return {
|
|
1111
|
+
changedInstanceIds: changed,
|
|
1112
|
+
result: {
|
|
1113
|
+
status: "applied",
|
|
1114
|
+
revision: operation.revision,
|
|
1115
|
+
operationId: operation.operationId
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
case "instance.remove": {
|
|
1120
|
+
const instance = this.instances.get(operation.instanceId);
|
|
1121
|
+
if (instance === void 0 || instance.parent === null) {
|
|
1122
|
+
return {
|
|
1123
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1124
|
+
result: rejectOperation(
|
|
1125
|
+
operation,
|
|
1126
|
+
this.revision,
|
|
1127
|
+
operationFailure(
|
|
1128
|
+
"INSTANCE_NOT_FOUND",
|
|
1129
|
+
"remove requires an existing non-root instance",
|
|
1130
|
+
instance,
|
|
1131
|
+
"remove"
|
|
1132
|
+
)
|
|
1133
|
+
)
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
const capabilityFailure = this.requireCapability(instance, "remove");
|
|
1137
|
+
const parent = this.instances.get(instance.parent.instanceId);
|
|
1138
|
+
const slot = this.components.get(parent?.componentId ?? "")?.slots.find((candidate) => candidate.slotId === instance.parent?.slotId);
|
|
1139
|
+
const siblings = parent === void 0 || slot === void 0 ? [] : childrenForSlot(this.instances, parent.instanceId, slot.slotId);
|
|
1140
|
+
if (capabilityFailure !== null || parent === void 0 || slot === void 0 || !slot.removableChildren || siblings.length <= slot.minInstances) {
|
|
1141
|
+
return {
|
|
1142
|
+
changedInstanceIds: /* @__PURE__ */ new Set(),
|
|
1143
|
+
result: rejectOperation(
|
|
1144
|
+
operation,
|
|
1145
|
+
this.revision,
|
|
1146
|
+
capabilityFailure ?? operationFailure(
|
|
1147
|
+
"SLOT_CONSTRAINT",
|
|
1148
|
+
"instance cannot be removed from this slot",
|
|
1149
|
+
instance,
|
|
1150
|
+
"remove"
|
|
1151
|
+
)
|
|
1152
|
+
)
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
const removeIds = /* @__PURE__ */ new Set();
|
|
1156
|
+
const collect = (instanceId) => {
|
|
1157
|
+
if (removeIds.has(instanceId)) return;
|
|
1158
|
+
removeIds.add(instanceId);
|
|
1159
|
+
for (const candidate of this.instances.values()) {
|
|
1160
|
+
if (candidate.parent?.instanceId === instanceId) collect(candidate.instanceId);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
collect(instance.instanceId);
|
|
1164
|
+
for (const instanceId of removeIds) {
|
|
1165
|
+
this.instances.delete(instanceId);
|
|
1166
|
+
this.mounted.delete(instanceId);
|
|
1167
|
+
}
|
|
1168
|
+
const changed = this.normalizeSlot(parent.instanceId, slot.slotId);
|
|
1169
|
+
for (const instanceId of removeIds) changed.add(instanceId);
|
|
1170
|
+
if (removeIds.has(this.selectedInstanceId ?? "")) this.selectedInstanceId = null;
|
|
1171
|
+
if (removeIds.has(this.hoveredInstanceId ?? "")) this.hoveredInstanceId = null;
|
|
1172
|
+
return {
|
|
1173
|
+
changedInstanceIds: changed,
|
|
1174
|
+
result: {
|
|
1175
|
+
status: "applied",
|
|
1176
|
+
revision: operation.revision,
|
|
1177
|
+
operationId: operation.operationId
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
apply(operation) {
|
|
1184
|
+
const signature = jsonSignature(operation);
|
|
1185
|
+
if (signature === null) {
|
|
1186
|
+
return rejectOperation(
|
|
1187
|
+
operation,
|
|
1188
|
+
this.revision,
|
|
1189
|
+
operationFailure("INVALID_OPERATION", "operation is not bounded JSON")
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
const previous = this.operations.get(operation.operationId);
|
|
1193
|
+
if (previous !== void 0) {
|
|
1194
|
+
return previous.signature === signature ? previous.result : rejectOperation(
|
|
1195
|
+
operation,
|
|
1196
|
+
this.revision,
|
|
1197
|
+
operationFailure(
|
|
1198
|
+
"DUPLICATE_OPERATION_CONFLICT",
|
|
1199
|
+
"operationId was reused with a different operation"
|
|
1200
|
+
)
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
if (this.operations.size >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxDraftOperations) {
|
|
1204
|
+
return rejectOperation(
|
|
1205
|
+
operation,
|
|
1206
|
+
this.revision,
|
|
1207
|
+
operationFailure("OPERATION_LIMIT_EXCEEDED", "draft operation limit reached")
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
if (operation.revision !== this.revision + 1) {
|
|
1211
|
+
const result = rejectOperation(
|
|
1212
|
+
operation,
|
|
1213
|
+
this.revision,
|
|
1214
|
+
operationFailure(
|
|
1215
|
+
"REVISION_MISMATCH",
|
|
1216
|
+
`expected preview revision ${this.revision + 1}, received ${operation.revision}`
|
|
1217
|
+
)
|
|
1218
|
+
);
|
|
1219
|
+
this.operations.set(operation.operationId, { signature, result });
|
|
1220
|
+
return result;
|
|
1221
|
+
}
|
|
1222
|
+
const mutation = this.applyValidatedOperation(operation);
|
|
1223
|
+
this.operations.set(operation.operationId, { signature, result: mutation.result });
|
|
1224
|
+
if (mutation.result.status === "applied") {
|
|
1225
|
+
this.revision = operation.revision;
|
|
1226
|
+
this.emit(mutation.changedInstanceIds);
|
|
1227
|
+
}
|
|
1228
|
+
return mutation.result;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
function canvasOperationForIntent(store, intent, operationId) {
|
|
1232
|
+
const revision = store.revision + 1;
|
|
1233
|
+
if (intent.type === "instance.move") {
|
|
1234
|
+
return {
|
|
1235
|
+
type: "instance.move",
|
|
1236
|
+
operationId,
|
|
1237
|
+
revision,
|
|
1238
|
+
instanceId: intent.instanceId,
|
|
1239
|
+
parentInstanceId: intent.parentInstanceId,
|
|
1240
|
+
slotId: intent.slotId,
|
|
1241
|
+
index: intent.index
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
const snapshot = store.getInstanceSnapshot(intent.instanceId);
|
|
1245
|
+
if (snapshot === null) {
|
|
1246
|
+
return operationFailure("INSTANCE_NOT_FOUND", `instance "${intent.instanceId}" is not declared`);
|
|
1247
|
+
}
|
|
1248
|
+
if (intent.type === "prop.inline") {
|
|
1249
|
+
const control2 = snapshot.component.props.find(
|
|
1250
|
+
(candidate) => candidate.propName === intent.propName
|
|
1251
|
+
);
|
|
1252
|
+
if (control2 === void 0 || !control2.allowedBindings.includes("inline") || (snapshot.instance.props[intent.propName] ?? control2.defaultBinding)?.kind === "external" || !snapshot.component.capabilities.includes("props") || !snapshot.component.capabilities.includes("inline-content")) {
|
|
1253
|
+
return operationFailure(
|
|
1254
|
+
"CAPABILITY_DENIED",
|
|
1255
|
+
`prop "${intent.propName}" is not an inline-editable canvas control`,
|
|
1256
|
+
snapshot.instance,
|
|
1257
|
+
"inline-content"
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
type: "prop.patch",
|
|
1262
|
+
operationId,
|
|
1263
|
+
revision,
|
|
1264
|
+
instanceId: intent.instanceId,
|
|
1265
|
+
propName: intent.propName,
|
|
1266
|
+
binding: { kind: "inline", value: intent.value }
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
const control = snapshot.component.styles.layout.find(
|
|
1270
|
+
(candidate) => candidate.controlId === intent.controlId
|
|
1271
|
+
);
|
|
1272
|
+
if (control === void 0 || !DIRECT_CANVAS_LAYOUT_CONTROLS.has(control.control) || !snapshot.component.capabilities.includes("styles") || intent.breakpointId !== void 0 && (!snapshot.component.styles.responsive || !snapshot.component.capabilities.includes("responsive"))) {
|
|
1273
|
+
return operationFailure(
|
|
1274
|
+
"CAPABILITY_DENIED",
|
|
1275
|
+
`layout control "${intent.controlId}" is not directly editable on the canvas`,
|
|
1276
|
+
snapshot.instance,
|
|
1277
|
+
intent.breakpointId === void 0 ? "styles" : "responsive"
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const value = `${intent.value}px`;
|
|
1281
|
+
if (control.allowedValues !== void 0 && !control.allowedValues.includes(value)) {
|
|
1282
|
+
return operationFailure(
|
|
1283
|
+
"CAPABILITY_DENIED",
|
|
1284
|
+
`layout value "${value}" is outside the declared allowed values`,
|
|
1285
|
+
snapshot.instance,
|
|
1286
|
+
"styles"
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
const styles = HydrogenInstanceStylesSchema.parse(snapshot.instance.styles);
|
|
1290
|
+
if (intent.breakpointId === void 0) {
|
|
1291
|
+
return {
|
|
1292
|
+
type: "styles.patch",
|
|
1293
|
+
operationId,
|
|
1294
|
+
revision,
|
|
1295
|
+
instanceId: intent.instanceId,
|
|
1296
|
+
styles: {
|
|
1297
|
+
...styles,
|
|
1298
|
+
base: {
|
|
1299
|
+
...styles.base,
|
|
1300
|
+
layout: { ...styles.base.layout, [intent.controlId]: value }
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
if (!store.manifest.breakpoints.some(
|
|
1306
|
+
(breakpoint) => breakpoint.breakpointId === intent.breakpointId
|
|
1307
|
+
)) {
|
|
1308
|
+
return operationFailure(
|
|
1309
|
+
"CAPABILITY_DENIED",
|
|
1310
|
+
`responsive breakpoint "${intent.breakpointId}" is unknown`,
|
|
1311
|
+
snapshot.instance,
|
|
1312
|
+
"responsive"
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
const existing = styles.overrides.find(
|
|
1316
|
+
(override2) => override2.breakpointId === intent.breakpointId
|
|
1317
|
+
);
|
|
1318
|
+
const override = {
|
|
1319
|
+
breakpointId: intent.breakpointId,
|
|
1320
|
+
values: {
|
|
1321
|
+
tokens: existing?.values.tokens ?? {},
|
|
1322
|
+
layout: { ...existing?.values.layout, [intent.controlId]: value }
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
return {
|
|
1326
|
+
type: "styles.patch",
|
|
1327
|
+
operationId,
|
|
1328
|
+
revision,
|
|
1329
|
+
instanceId: intent.instanceId,
|
|
1330
|
+
styles: {
|
|
1331
|
+
...styles,
|
|
1332
|
+
overrides: [
|
|
1333
|
+
...styles.overrides.filter((candidate) => candidate.breakpointId !== intent.breakpointId),
|
|
1334
|
+
override
|
|
1335
|
+
]
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function validateCanvasInsertRequest(store, request) {
|
|
1340
|
+
const parent = store.getInstanceSnapshot(request.parentInstanceId);
|
|
1341
|
+
if (parent === null) {
|
|
1342
|
+
return operationFailure(
|
|
1343
|
+
"INSTANCE_NOT_FOUND",
|
|
1344
|
+
`parent instance "${request.parentInstanceId}" is not declared`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
const slot = parent.component.slots.find((candidate) => candidate.slotId === request.slotId);
|
|
1348
|
+
const children = parent.childInstanceIds[request.slotId];
|
|
1349
|
+
if (slot === void 0 || slot.insertion === "fixed" || !parent.component.capabilities.includes("insert") || children === void 0 || children.length >= slot.maxInstances || request.index > children.length) {
|
|
1350
|
+
return operationFailure(
|
|
1351
|
+
"SLOT_CONSTRAINT",
|
|
1352
|
+
`slot "${request.slotId}" does not accept an insertion at index ${request.index}`,
|
|
1353
|
+
parent.instance,
|
|
1354
|
+
"insert"
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
const blocking = parent.capabilityReasons.find(
|
|
1358
|
+
(reason) => reason.severity === "blocking" && (reason.capability === void 0 || reason.capability === "insert")
|
|
1359
|
+
);
|
|
1360
|
+
return blocking === void 0 ? null : operationFailure(
|
|
1361
|
+
"UNSUPPORTED_BOUNDARY",
|
|
1362
|
+
blocking.message,
|
|
1363
|
+
parent.instance,
|
|
1364
|
+
blocking.capability ?? "insert"
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
function readyDocument(store) {
|
|
1368
|
+
const snapshot = store.getSnapshot();
|
|
1369
|
+
const instances = snapshot.instanceIds.flatMap((instanceId) => {
|
|
1370
|
+
const instance = store.getInstanceSnapshot(instanceId);
|
|
1371
|
+
return instance === null ? [] : [
|
|
1372
|
+
{
|
|
1373
|
+
instance: instance.instance,
|
|
1374
|
+
mounted: instance.mounted,
|
|
1375
|
+
editable: instance.editable,
|
|
1376
|
+
capabilityReasons: instance.capabilityReasons
|
|
1377
|
+
}
|
|
1378
|
+
];
|
|
1379
|
+
});
|
|
1380
|
+
const componentIds = new Set(instances.map(({ instance }) => instance.componentId));
|
|
1381
|
+
let discoveredComponent = true;
|
|
1382
|
+
while (discoveredComponent) {
|
|
1383
|
+
discoveredComponent = false;
|
|
1384
|
+
for (const componentId of [...componentIds]) {
|
|
1385
|
+
const component = store.components.get(componentId);
|
|
1386
|
+
for (const slot of component?.slots ?? []) {
|
|
1387
|
+
const candidates = slot.accepts.mode === "any" ? [...store.components.keys()] : slot.accepts.componentIds;
|
|
1388
|
+
for (const candidate of candidates) {
|
|
1389
|
+
if (componentIds.has(candidate)) continue;
|
|
1390
|
+
componentIds.add(candidate);
|
|
1391
|
+
discoveredComponent = true;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const components = [...componentIds].flatMap((componentId) => {
|
|
1397
|
+
const component = store.components.get(componentId);
|
|
1398
|
+
return component === void 0 ? [] : [component];
|
|
1399
|
+
});
|
|
1400
|
+
const instanceIds = new Set(instances.map(({ instance }) => instance.instanceId));
|
|
1401
|
+
const componentAnchorIds = new Set(components.map((component) => component.source.anchorId));
|
|
1402
|
+
const instanceAnchorIds = new Set(instances.map(({ instance }) => instance.source.anchorId));
|
|
1403
|
+
const unsupportedFindings = store.manifest.unsupportedFindings.filter((finding) => {
|
|
1404
|
+
switch (finding.target.kind) {
|
|
1405
|
+
case "manifest":
|
|
1406
|
+
return true;
|
|
1407
|
+
case "component":
|
|
1408
|
+
return componentIds.has(finding.target.componentId);
|
|
1409
|
+
case "instance":
|
|
1410
|
+
return instanceIds.has(finding.target.instanceId);
|
|
1411
|
+
case "source":
|
|
1412
|
+
return componentAnchorIds.has(finding.target.anchorId) || instanceAnchorIds.has(finding.target.anchorId);
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
return {
|
|
1416
|
+
protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
|
|
1417
|
+
document: snapshot.document,
|
|
1418
|
+
breakpoints: store.manifest.breakpoints,
|
|
1419
|
+
...snapshot.route !== void 0 ? { route: snapshot.route } : {},
|
|
1420
|
+
revision: snapshot.revision,
|
|
1421
|
+
selectedInstanceId: snapshot.selectedInstanceId,
|
|
1422
|
+
hoveredInstanceId: snapshot.hoveredInstanceId,
|
|
1423
|
+
selectionStatus: snapshot.selectionStatus,
|
|
1424
|
+
components,
|
|
1425
|
+
instances,
|
|
1426
|
+
unsupportedFindings
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
function createHydrogenEditorSourceProjection({
|
|
1430
|
+
manifest,
|
|
1431
|
+
documentId,
|
|
1432
|
+
route,
|
|
1433
|
+
revision = 0,
|
|
1434
|
+
selectedInstanceId
|
|
1435
|
+
}) {
|
|
1436
|
+
if (!isNonnegativeInteger(revision)) {
|
|
1437
|
+
throw new HydrogenEditorConfigurationError("revision must be a nonnegative integer");
|
|
1438
|
+
}
|
|
1439
|
+
const parsedManifest = HydrogenEditorManifestSchema.parse(manifest);
|
|
1440
|
+
const document = parsedManifest.documents.find((candidate) => candidate.documentId === documentId);
|
|
1441
|
+
if (document === void 0) {
|
|
1442
|
+
throw new HydrogenEditorConfigurationError(`unknown Hydrogen document "${documentId}"`);
|
|
1443
|
+
}
|
|
1444
|
+
const store = new HydrogenEditorPreviewStoreImpl(
|
|
1445
|
+
parsedManifest,
|
|
1446
|
+
documentId,
|
|
1447
|
+
route ?? document.route
|
|
1448
|
+
);
|
|
1449
|
+
store.revision = revision;
|
|
1450
|
+
const selection = selectedInstanceId === void 0 ? document.rootInstanceIds[0] ?? parsedManifest.instances.find((instance) => instance.documentId === documentId)?.instanceId ?? null : selectedInstanceId;
|
|
1451
|
+
if (!store.setSelection(selection)) {
|
|
1452
|
+
throw new HydrogenEditorConfigurationError(
|
|
1453
|
+
`unknown Hydrogen instance "${selectedInstanceId ?? ""}"`
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
return readyDocument(store);
|
|
1457
|
+
}
|
|
1458
|
+
function reconciliationDocument(store, reason) {
|
|
1459
|
+
const snapshot = store.getSnapshot();
|
|
1460
|
+
return {
|
|
1461
|
+
reason,
|
|
1462
|
+
documentId: snapshot.document.documentId,
|
|
1463
|
+
...snapshot.route !== void 0 ? { route: snapshot.route } : {},
|
|
1464
|
+
revision: snapshot.revision,
|
|
1465
|
+
selectedInstanceId: snapshot.selectedInstanceId,
|
|
1466
|
+
selectionStatus: snapshot.selectionStatus,
|
|
1467
|
+
mountedInstanceIds: snapshot.mountedInstanceIds
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function isNullableOpaqueId(value) {
|
|
1471
|
+
return value === null || isOpaqueId(value);
|
|
1472
|
+
}
|
|
1473
|
+
function parseReadyDocument(value, expectedManifest, submittedInstances) {
|
|
1474
|
+
if (!isRecord(value) || value.protocolVersion !== HYDROGEN_EDITOR_PROTOCOL_VERSION || !isNonnegativeInteger(value.revision) || !isNullableOpaqueId(value.selectedInstanceId) || !isNullableOpaqueId(value.hoveredInstanceId) || value.selectionStatus !== "active" && value.selectionStatus !== "out-of-route" && value.selectionStatus !== "none" || value.route !== void 0 && typeof value.route !== "string" || !Array.isArray(value.breakpoints) || !Array.isArray(value.components) || !Array.isArray(value.instances) || !Array.isArray(value.unsupportedFindings)) {
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
const document = HydrogenDocumentSchema.safeParse(value.document);
|
|
1478
|
+
const breakpoints = value.breakpoints.map(
|
|
1479
|
+
(breakpoint) => HydrogenBreakpointSchema.safeParse(breakpoint)
|
|
1480
|
+
);
|
|
1481
|
+
const components = value.components.map(
|
|
1482
|
+
(component) => HydrogenComponentDefinitionSchema.safeParse(component)
|
|
1483
|
+
);
|
|
1484
|
+
const instances = value.instances.map((candidate) => {
|
|
1485
|
+
if (!isRecord(candidate) || typeof candidate.mounted !== "boolean" || typeof candidate.editable !== "boolean" || !Array.isArray(candidate.capabilityReasons)) {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
const instance = HydrogenInstanceSchema.safeParse(candidate.instance);
|
|
1489
|
+
if (!instance.success) return null;
|
|
1490
|
+
const reasons = candidate.capabilityReasons.flatMap((reason) => {
|
|
1491
|
+
if (!isRecord(reason) || typeof reason.code !== "string" || reason.code.length === 0 || reason.code.length > 128 || reason.severity !== "warning" && reason.severity !== "blocking" || typeof reason.message !== "string" || reason.message.length === 0 || reason.message.length > 2048) {
|
|
1492
|
+
return [];
|
|
1493
|
+
}
|
|
1494
|
+
const capability = reason.capability === void 0 ? void 0 : HydrogenEditCapabilitySchema.safeParse(reason.capability);
|
|
1495
|
+
const source = reason.source === void 0 ? void 0 : HydrogenSourceLocationSchema.safeParse(reason.source);
|
|
1496
|
+
if (capability !== void 0 && !capability.success) return [];
|
|
1497
|
+
if (source !== void 0 && !source.success) return [];
|
|
1498
|
+
return [
|
|
1499
|
+
{
|
|
1500
|
+
code: reason.code,
|
|
1501
|
+
severity: reason.severity,
|
|
1502
|
+
message: reason.message,
|
|
1503
|
+
...capability?.success ? { capability: capability.data } : {},
|
|
1504
|
+
...source?.success ? { source: source.data } : {}
|
|
1505
|
+
}
|
|
1506
|
+
];
|
|
1507
|
+
});
|
|
1508
|
+
if (reasons.length !== candidate.capabilityReasons.length) return null;
|
|
1509
|
+
return {
|
|
1510
|
+
instance: instance.data,
|
|
1511
|
+
mounted: candidate.mounted,
|
|
1512
|
+
editable: candidate.editable,
|
|
1513
|
+
capabilityReasons: reasons
|
|
1514
|
+
};
|
|
1515
|
+
});
|
|
1516
|
+
if (!document.success || breakpoints.some((breakpoint) => !breakpoint.success) || components.some((component) => !component.success) || value.breakpoints.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxBreakpoints || instances.some((instance) => instance === null) || value.components.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxComponents || value.instances.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxInstancesPerDocument) {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
const unsupportedFindings = value.unsupportedFindings.map(
|
|
1520
|
+
(finding) => HydrogenUnsupportedFindingSchema.safeParse(finding)
|
|
1521
|
+
);
|
|
1522
|
+
if (unsupportedFindings.some((finding) => !finding.success) || unsupportedFindings.length > HYDROGEN_EDITOR_MANIFEST_LIMITS.maxFindings) {
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
const parsedComponents = components.flatMap(
|
|
1526
|
+
(component) => component.success ? [component.data] : []
|
|
1527
|
+
);
|
|
1528
|
+
const parsedBreakpoints = breakpoints.flatMap(
|
|
1529
|
+
(breakpoint) => breakpoint.success ? [breakpoint.data] : []
|
|
1530
|
+
);
|
|
1531
|
+
const parsedInstances = instances.flatMap((instance) => instance === null ? [] : [instance]);
|
|
1532
|
+
const parsedFindings = unsupportedFindings.flatMap(
|
|
1533
|
+
(finding) => finding.success ? [finding.data] : []
|
|
1534
|
+
);
|
|
1535
|
+
const expectedDocument = expectedManifest.documents.find(
|
|
1536
|
+
(candidate) => candidate.documentId === document.data.documentId
|
|
1537
|
+
);
|
|
1538
|
+
if (expectedDocument === void 0 || jsonSignature(expectedDocument) !== jsonSignature(document.data) || jsonSignature(expectedManifest.breakpoints) !== jsonSignature(parsedBreakpoints)) {
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
const expectedComponents = new Map(
|
|
1542
|
+
expectedManifest.components.map((component) => [component.componentId, component])
|
|
1543
|
+
);
|
|
1544
|
+
const componentIds = /* @__PURE__ */ new Set();
|
|
1545
|
+
for (const component of parsedComponents) {
|
|
1546
|
+
const expected = expectedComponents.get(component.componentId);
|
|
1547
|
+
if (componentIds.has(component.componentId) || expected === void 0 || jsonSignature(expected) !== jsonSignature(component)) {
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
componentIds.add(component.componentId);
|
|
1551
|
+
}
|
|
1552
|
+
const expectedInstances = new Map(
|
|
1553
|
+
expectedManifest.instances.map((instance) => [instance.instanceId, instance])
|
|
1554
|
+
);
|
|
1555
|
+
const instanceIds = /* @__PURE__ */ new Set();
|
|
1556
|
+
const sourceAnchors = new Set(parsedComponents.map((component) => component.source.anchorId));
|
|
1557
|
+
const normalizedInstances = [];
|
|
1558
|
+
for (const candidate of parsedInstances) {
|
|
1559
|
+
const instance = candidate.instance;
|
|
1560
|
+
const component = expectedComponents.get(instance.componentId);
|
|
1561
|
+
const expected = expectedInstances.get(instance.instanceId);
|
|
1562
|
+
const submitted = submittedInstances.get(instance.instanceId);
|
|
1563
|
+
const trustedIdentity = expected ?? submitted;
|
|
1564
|
+
if (instanceIds.has(instance.instanceId) || sourceAnchors.has(instance.source.anchorId) || component === void 0 || !componentIds.has(component.componentId) || trustedIdentity === void 0 || trustedIdentity.componentId !== instance.componentId || trustedIdentity.documentId !== instance.documentId || jsonSignature(trustedIdentity.source) !== jsonSignature(instance.source) || instance.documentId !== document.data.documentId || !instanceMatchesComponentContract(expectedManifest, instance, component)) {
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
instanceIds.add(instance.instanceId);
|
|
1568
|
+
sourceAnchors.add(instance.source.anchorId);
|
|
1569
|
+
const capabilityReasons = findingsForInstance(expectedManifest, instance, component);
|
|
1570
|
+
normalizedInstances.push({
|
|
1571
|
+
instance,
|
|
1572
|
+
mounted: candidate.mounted,
|
|
1573
|
+
editable: component.capabilities.length > 0 && !capabilityReasons.some((reason) => reason.severity === "blocking"),
|
|
1574
|
+
capabilityReasons
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
for (const candidate of normalizedInstances) {
|
|
1578
|
+
const parent = candidate.instance.parent;
|
|
1579
|
+
if (parent === null) {
|
|
1580
|
+
if (!document.data.rootInstanceIds.includes(candidate.instance.instanceId)) return null;
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
const parentInstance = normalizedInstances.find(
|
|
1584
|
+
(item) => item.instance.instanceId === parent.instanceId
|
|
1585
|
+
)?.instance;
|
|
1586
|
+
const parentComponent = parentInstance === void 0 ? void 0 : expectedComponents.get(parentInstance.componentId);
|
|
1587
|
+
const slot = parentComponent?.slots.find((item) => item.slotId === parent.slotId);
|
|
1588
|
+
if (parentInstance === void 0 || slot === void 0 || slot.accepts.mode === "include" && !slot.accepts.componentIds.includes(candidate.instance.componentId)) {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (document.data.rootInstanceIds.some((instanceId) => !instanceIds.has(instanceId)) || value.selectedInstanceId !== null && !expectedInstances.has(value.selectedInstanceId) && !submittedInstances.has(value.selectedInstanceId) || value.hoveredInstanceId !== null && !instanceIds.has(value.hoveredInstanceId)) {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
const expectedFindingSignatures = new Set(
|
|
1596
|
+
expectedManifest.unsupportedFindings.filter((finding) => {
|
|
1597
|
+
switch (finding.target.kind) {
|
|
1598
|
+
case "manifest":
|
|
1599
|
+
return true;
|
|
1600
|
+
case "component":
|
|
1601
|
+
return componentIds.has(finding.target.componentId);
|
|
1602
|
+
case "instance":
|
|
1603
|
+
return instanceIds.has(finding.target.instanceId);
|
|
1604
|
+
case "source":
|
|
1605
|
+
return sourceAnchors.has(finding.target.anchorId);
|
|
1606
|
+
}
|
|
1607
|
+
}).map((finding) => jsonSignature(finding))
|
|
1608
|
+
);
|
|
1609
|
+
const receivedFindingSignatures = new Set(parsedFindings.map((finding) => jsonSignature(finding)));
|
|
1610
|
+
if (receivedFindingSignatures.size !== expectedFindingSignatures.size || [...receivedFindingSignatures].some((signature) => !expectedFindingSignatures.has(signature))) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
return {
|
|
1614
|
+
protocolVersion: HYDROGEN_EDITOR_PROTOCOL_VERSION,
|
|
1615
|
+
document: document.data,
|
|
1616
|
+
breakpoints: parsedBreakpoints,
|
|
1617
|
+
...value.route !== void 0 ? { route: value.route } : {},
|
|
1618
|
+
revision: value.revision,
|
|
1619
|
+
selectedInstanceId: value.selectedInstanceId,
|
|
1620
|
+
hoveredInstanceId: value.hoveredInstanceId,
|
|
1621
|
+
selectionStatus: value.selectionStatus,
|
|
1622
|
+
components: parsedComponents,
|
|
1623
|
+
instances: normalizedInstances,
|
|
1624
|
+
unsupportedFindings: parsedFindings
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
function parseApplyResult(value) {
|
|
1628
|
+
if (!isRecord(value) || value.status !== "applied" && value.status !== "duplicate" && value.status !== "rejected" || !isNonnegativeInteger(value.revision) || !isOpaqueId(value.operationId)) {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
if (value.status === "rejected") {
|
|
1632
|
+
if (!isRecord(value.failure) || typeof value.failure.code !== "string" || !OPERATION_FAILURE_CODES.has(value.failure.code) || typeof value.failure.message !== "string" || value.failure.message.length > 2048 || value.failure.instanceId !== void 0 && !isOpaqueId(value.failure.instanceId)) {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
const capability = value.failure.capability === void 0 ? void 0 : HydrogenEditCapabilitySchema.safeParse(value.failure.capability);
|
|
1636
|
+
const source = value.failure.source === void 0 ? void 0 : HydrogenSourceLocationSchema.safeParse(value.failure.source);
|
|
1637
|
+
if (capability !== void 0 && !capability.success) return null;
|
|
1638
|
+
if (source !== void 0 && !source.success) return null;
|
|
1639
|
+
return {
|
|
1640
|
+
status: "rejected",
|
|
1641
|
+
revision: value.revision,
|
|
1642
|
+
operationId: value.operationId,
|
|
1643
|
+
failure: {
|
|
1644
|
+
code: value.failure.code,
|
|
1645
|
+
message: value.failure.message,
|
|
1646
|
+
...value.failure.instanceId !== void 0 ? { instanceId: value.failure.instanceId } : {},
|
|
1647
|
+
...capability?.success ? { capability: capability.data } : {},
|
|
1648
|
+
...source?.success ? { source: source.data } : {}
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
status: value.status,
|
|
1654
|
+
revision: value.revision,
|
|
1655
|
+
operationId: value.operationId
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
function parseCanvasOperationEvent(value) {
|
|
1659
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["intent", "operation", "result"])) return null;
|
|
1660
|
+
const intent = parseHydrogenEditorCanvasIntent(value.intent);
|
|
1661
|
+
const operation = parseOperation(value.operation);
|
|
1662
|
+
const result = parseApplyResult(value.result);
|
|
1663
|
+
if (intent === null || intent.type === "slot.insert.request" || operation === null || result === null || operation.operationId !== result.operationId) {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
const matches = intent.type === "instance.move" && operation.type === "instance.move" && operation.instanceId === intent.instanceId && operation.parentInstanceId === intent.parentInstanceId && operation.slotId === intent.slotId && operation.index === intent.index || intent.type === "prop.inline" && operation.type === "prop.patch" && operation.instanceId === intent.instanceId && operation.propName === intent.propName && operation.binding.kind === "inline" && operation.binding.value === intent.value || intent.type === "styles.adjust" && operation.type === "styles.patch" && operation.instanceId === intent.instanceId;
|
|
1667
|
+
return matches ? { intent, operation, result } : null;
|
|
1668
|
+
}
|
|
1669
|
+
function parseReconciliation(value) {
|
|
1670
|
+
if (!isRecord(value) || value.reason !== "route" && value.reason !== "loader" && value.reason !== "suspense" && value.reason !== "hmr" || !isOpaqueId(value.documentId) || value.route !== void 0 && typeof value.route !== "string" || !isNonnegativeInteger(value.revision) || !isNullableOpaqueId(value.selectedInstanceId) || value.selectionStatus !== "active" && value.selectionStatus !== "out-of-route" && value.selectionStatus !== "none" || !Array.isArray(value.mountedInstanceIds) || !value.mountedInstanceIds.every(isOpaqueId)) {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
return value;
|
|
1674
|
+
}
|
|
1675
|
+
function createHydrogenEditorPreviewRuntime({
|
|
1676
|
+
adminOrigin,
|
|
1677
|
+
documentId,
|
|
1678
|
+
manifest,
|
|
1679
|
+
now = Date.now,
|
|
1680
|
+
onProtocolFailure,
|
|
1681
|
+
route,
|
|
1682
|
+
security,
|
|
1683
|
+
target,
|
|
1684
|
+
transport
|
|
1685
|
+
}) {
|
|
1686
|
+
const trustedAdminOrigin = exactOrigin(adminOrigin, "adminOrigin");
|
|
1687
|
+
validateSecurityContext(security, now());
|
|
1688
|
+
const parsedManifest = HydrogenEditorManifestSchema.parse(manifest);
|
|
1689
|
+
if (parsedManifest.source.baseSha !== security.baseSha) {
|
|
1690
|
+
throw new HydrogenEditorConfigurationError(
|
|
1691
|
+
"manifest source baseSha does not match the editor session"
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
const store = new HydrogenEditorPreviewStoreImpl(parsedManifest, documentId, route);
|
|
1695
|
+
const inbound = { sequence: 0, messages: /* @__PURE__ */ new Map() };
|
|
1696
|
+
const buffered = [];
|
|
1697
|
+
let outboundSequence = 0;
|
|
1698
|
+
let canvasOperationSequence = 0;
|
|
1699
|
+
let ready = false;
|
|
1700
|
+
let disposed = false;
|
|
1701
|
+
const reportProtocolFailure = (failure) => {
|
|
1702
|
+
onProtocolFailure?.(failure);
|
|
1703
|
+
};
|
|
1704
|
+
const post = (type, payload) => {
|
|
1705
|
+
if (now() >= security.expiresAt) {
|
|
1706
|
+
reportProtocolFailure(protocolFailure("MESSAGE_EXPIRED", "editor preview token has expired"));
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
outboundSequence += 1;
|
|
1710
|
+
postEnvelope(
|
|
1711
|
+
target,
|
|
1712
|
+
trustedAdminOrigin,
|
|
1713
|
+
createEnvelope(PREVIEW_SOURCE, security, outboundSequence, type, payload)
|
|
1714
|
+
);
|
|
1715
|
+
};
|
|
1716
|
+
const postReady = () => {
|
|
1717
|
+
try {
|
|
1718
|
+
post("editor.ready", readyDocument(store));
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
const failure = protocolFailure(
|
|
1721
|
+
"MESSAGE_TOO_LARGE",
|
|
1722
|
+
error instanceof Error ? error.message : "ready document exceeds bridge bounds"
|
|
1723
|
+
);
|
|
1724
|
+
reportProtocolFailure(failure);
|
|
1725
|
+
post("editor.protocol.failure", failure);
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
const process = (envelope) => {
|
|
1729
|
+
if (now() >= security.expiresAt) {
|
|
1730
|
+
reportProtocolFailure(
|
|
1731
|
+
protocolFailure("MESSAGE_EXPIRED", "buffered editor message expired before ready")
|
|
1732
|
+
);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
switch (envelope.type) {
|
|
1736
|
+
case "editor.protocol.failure": {
|
|
1737
|
+
const failure = parseProtocolFailure(envelope.payload);
|
|
1738
|
+
onProtocolFailure?.(
|
|
1739
|
+
failure ?? protocolFailure("UNKNOWN_MESSAGE", "preview protocol failure payload is invalid")
|
|
1740
|
+
);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
case "editor.ready.request":
|
|
1744
|
+
postReady();
|
|
1745
|
+
return;
|
|
1746
|
+
case "instance.hover": {
|
|
1747
|
+
const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
|
|
1748
|
+
if (instanceId === void 0 || !store.setHover(instanceId)) {
|
|
1749
|
+
reportProtocolFailure(
|
|
1750
|
+
protocolFailure("UNKNOWN_MESSAGE", "hover targets an unknown instance")
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
case "instance.select": {
|
|
1756
|
+
const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
|
|
1757
|
+
if (instanceId === void 0 || !store.setSelection(instanceId)) {
|
|
1758
|
+
reportProtocolFailure(
|
|
1759
|
+
protocolFailure("UNKNOWN_MESSAGE", "selection targets an unknown instance")
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
case "preview.operation.apply": {
|
|
1765
|
+
const operation = parseOperation(envelope.payload);
|
|
1766
|
+
const result = operation === null ? rejectOperation(
|
|
1767
|
+
{ operationId: "invalid" },
|
|
1768
|
+
store.revision,
|
|
1769
|
+
operationFailure("INVALID_OPERATION", "operation payload is invalid")
|
|
1770
|
+
) : store.apply(operation);
|
|
1771
|
+
post("preview.operation.result", result);
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
default:
|
|
1775
|
+
reportProtocolFailure(
|
|
1776
|
+
protocolFailure("UNKNOWN_MESSAGE", `unknown host message "${envelope.type}"`)
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
const onMessage = (event) => {
|
|
1781
|
+
if (disposed || event.origin !== trustedAdminOrigin) return;
|
|
1782
|
+
if (event.source !== target) return;
|
|
1783
|
+
const parsed = parseEnvelope(event.data, HOST_SOURCE, security, inbound, now());
|
|
1784
|
+
if ("code" in parsed) {
|
|
1785
|
+
reportProtocolFailure(parsed);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (parsed.duplicate) return;
|
|
1789
|
+
if (!ready) {
|
|
1790
|
+
if (buffered.length >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxBufferedMessages) {
|
|
1791
|
+
reportProtocolFailure(
|
|
1792
|
+
protocolFailure(
|
|
1793
|
+
"BUFFER_LIMIT_EXCEEDED",
|
|
1794
|
+
"pre-ready Hydrogen editor message buffer is full"
|
|
1795
|
+
)
|
|
1796
|
+
);
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
buffered.push(parsed.envelope);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
process(parsed.envelope);
|
|
1803
|
+
};
|
|
1804
|
+
transport.addEventListener("message", onMessage);
|
|
1805
|
+
return {
|
|
1806
|
+
manifest: parsedManifest,
|
|
1807
|
+
store,
|
|
1808
|
+
ready: () => {
|
|
1809
|
+
if (disposed) return;
|
|
1810
|
+
ready = true;
|
|
1811
|
+
postReady();
|
|
1812
|
+
while (buffered.length > 0) {
|
|
1813
|
+
const envelope = buffered.shift();
|
|
1814
|
+
if (envelope !== void 0) process(envelope);
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
reconcile: (nextDocumentId, reason, nextRoute) => {
|
|
1818
|
+
if (disposed) return;
|
|
1819
|
+
store.reconcile(nextDocumentId, nextRoute);
|
|
1820
|
+
if (ready) post("editor.reconciled", reconciliationDocument(store, reason));
|
|
1821
|
+
},
|
|
1822
|
+
registerInstance: (instanceId) => {
|
|
1823
|
+
if (disposed || store.getInstanceSnapshot(instanceId) === null) return () => void 0;
|
|
1824
|
+
store.setMounted(instanceId, true);
|
|
1825
|
+
if (ready && store.selectedInstanceId === instanceId) {
|
|
1826
|
+
post("editor.reconciled", reconciliationDocument(store, "suspense"));
|
|
1827
|
+
}
|
|
1828
|
+
return () => {
|
|
1829
|
+
store.setMounted(instanceId, false);
|
|
1830
|
+
if (ready && store.selectedInstanceId === instanceId) {
|
|
1831
|
+
post("editor.reconciled", reconciliationDocument(store, "suspense"));
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
},
|
|
1835
|
+
hoverInstance: (instanceId) => {
|
|
1836
|
+
if (disposed || !store.setHover(instanceId)) return;
|
|
1837
|
+
if (ready) post("instance.hover", { instanceId });
|
|
1838
|
+
},
|
|
1839
|
+
selectInstance: (instanceId) => {
|
|
1840
|
+
if (disposed || !store.setSelection(instanceId)) return;
|
|
1841
|
+
if (ready) post("instance.select", { instanceId });
|
|
1842
|
+
},
|
|
1843
|
+
dispatchCanvasIntent: (value) => {
|
|
1844
|
+
const intent = parseHydrogenEditorCanvasIntent(value);
|
|
1845
|
+
if (disposed || !ready || intent === null) {
|
|
1846
|
+
return {
|
|
1847
|
+
status: "rejected",
|
|
1848
|
+
revision: store.revision,
|
|
1849
|
+
failure: operationFailure(
|
|
1850
|
+
"INVALID_OPERATION",
|
|
1851
|
+
disposed ? "editor preview runtime is disposed" : !ready ? "editor preview runtime is not ready" : "canvas intent is invalid"
|
|
1852
|
+
)
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
if (intent.type === "slot.insert.request") {
|
|
1856
|
+
const failure = validateCanvasInsertRequest(store, intent);
|
|
1857
|
+
if (failure !== null) {
|
|
1858
|
+
return { status: "rejected", revision: store.revision, failure };
|
|
1859
|
+
}
|
|
1860
|
+
post("canvas.insert.request", intent);
|
|
1861
|
+
return { status: "insert-requested", intent, revision: store.revision };
|
|
1862
|
+
}
|
|
1863
|
+
canvasOperationSequence += 1;
|
|
1864
|
+
const operation = canvasOperationForIntent(store, intent, `canvas:${canvasOperationSequence}`);
|
|
1865
|
+
if ("code" in operation) {
|
|
1866
|
+
return {
|
|
1867
|
+
status: "rejected",
|
|
1868
|
+
revision: store.revision,
|
|
1869
|
+
failure: operation
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
const result = store.apply(operation);
|
|
1873
|
+
post("canvas.operation", { intent, operation, result });
|
|
1874
|
+
return { status: "operation", intent, operation, result };
|
|
1875
|
+
},
|
|
1876
|
+
dispose: () => {
|
|
1877
|
+
if (disposed) return;
|
|
1878
|
+
disposed = true;
|
|
1879
|
+
buffered.length = 0;
|
|
1880
|
+
transport.removeEventListener("message", onMessage);
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
function createHydrogenEditorHostBridge({
|
|
1885
|
+
manifest,
|
|
1886
|
+
now = Date.now,
|
|
1887
|
+
onCanvasInsertRequest,
|
|
1888
|
+
onCanvasOperation,
|
|
1889
|
+
onHoverInstance,
|
|
1890
|
+
onOperationResult,
|
|
1891
|
+
onProtocolFailure,
|
|
1892
|
+
onReady,
|
|
1893
|
+
onReconcile,
|
|
1894
|
+
onSelectInstance,
|
|
1895
|
+
previewOrigin,
|
|
1896
|
+
resolveTarget,
|
|
1897
|
+
security,
|
|
1898
|
+
target,
|
|
1899
|
+
transport
|
|
1900
|
+
}) {
|
|
1901
|
+
const trustedPreviewOrigin = exactOrigin(previewOrigin, "previewOrigin");
|
|
1902
|
+
validateSecurityContext(security, now());
|
|
1903
|
+
const expectedManifest = cloneManifest(manifest);
|
|
1904
|
+
if (expectedManifest.source.baseSha !== security.baseSha) {
|
|
1905
|
+
throw new HydrogenEditorConfigurationError(
|
|
1906
|
+
"manifest source baseSha does not match the editor session"
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
const inbound = { sequence: 0, messages: /* @__PURE__ */ new Map() };
|
|
1910
|
+
const buffered = [];
|
|
1911
|
+
const submittedInstances = /* @__PURE__ */ new Map();
|
|
1912
|
+
let outboundSequence = 0;
|
|
1913
|
+
let ready = false;
|
|
1914
|
+
let disposed = false;
|
|
1915
|
+
const currentTarget = () => resolveTarget?.() ?? target;
|
|
1916
|
+
const post = (type, payload) => {
|
|
1917
|
+
if (now() >= security.expiresAt) {
|
|
1918
|
+
throw new HydrogenEditorConfigurationError("editor preview security context is expired");
|
|
1919
|
+
}
|
|
1920
|
+
outboundSequence += 1;
|
|
1921
|
+
postEnvelope(
|
|
1922
|
+
currentTarget(),
|
|
1923
|
+
trustedPreviewOrigin,
|
|
1924
|
+
createEnvelope(HOST_SOURCE, security, outboundSequence, type, payload)
|
|
1925
|
+
);
|
|
1926
|
+
};
|
|
1927
|
+
const process = (envelope) => {
|
|
1928
|
+
switch (envelope.type) {
|
|
1929
|
+
case "editor.protocol.failure": {
|
|
1930
|
+
const failure = parseProtocolFailure(envelope.payload);
|
|
1931
|
+
onProtocolFailure?.(
|
|
1932
|
+
failure ?? protocolFailure("UNKNOWN_MESSAGE", "preview protocol failure payload is invalid")
|
|
1933
|
+
);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
case "instance.hover":
|
|
1937
|
+
case "instance.select": {
|
|
1938
|
+
const instanceId = isRecord(envelope.payload) && isNullableOpaqueId(envelope.payload.instanceId) ? envelope.payload.instanceId : void 0;
|
|
1939
|
+
if (instanceId === void 0) {
|
|
1940
|
+
onProtocolFailure?.(
|
|
1941
|
+
protocolFailure("UNKNOWN_MESSAGE", `${envelope.type} payload is invalid`)
|
|
1942
|
+
);
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
if (envelope.type === "instance.hover") onHoverInstance?.(instanceId);
|
|
1946
|
+
else onSelectInstance?.(instanceId);
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
case "preview.operation.result": {
|
|
1950
|
+
const result = parseApplyResult(envelope.payload);
|
|
1951
|
+
if (result === null) {
|
|
1952
|
+
onProtocolFailure?.(
|
|
1953
|
+
protocolFailure("UNKNOWN_MESSAGE", "operation result payload is invalid")
|
|
1954
|
+
);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
onOperationResult?.(result);
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
case "canvas.operation": {
|
|
1961
|
+
const event = parseCanvasOperationEvent(envelope.payload);
|
|
1962
|
+
if (event === null) {
|
|
1963
|
+
onProtocolFailure?.(
|
|
1964
|
+
protocolFailure("UNKNOWN_MESSAGE", "canvas operation payload is invalid")
|
|
1965
|
+
);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
onOperationResult?.(event.result);
|
|
1969
|
+
onCanvasOperation?.(event.intent, event.operation, event.result);
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
case "canvas.insert.request": {
|
|
1973
|
+
const request = parseHydrogenEditorCanvasIntent(envelope.payload);
|
|
1974
|
+
if (request === null || request.type !== "slot.insert.request") {
|
|
1975
|
+
onProtocolFailure?.(
|
|
1976
|
+
protocolFailure("UNKNOWN_MESSAGE", "canvas insertion request payload is invalid")
|
|
1977
|
+
);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
onCanvasInsertRequest?.(request);
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
case "editor.reconciled": {
|
|
1984
|
+
const reconciliation = parseReconciliation(envelope.payload);
|
|
1985
|
+
if (reconciliation === null) {
|
|
1986
|
+
onProtocolFailure?.(
|
|
1987
|
+
protocolFailure("UNKNOWN_MESSAGE", "reconciliation payload is invalid")
|
|
1988
|
+
);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
onReconcile?.(reconciliation);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
default:
|
|
1995
|
+
onProtocolFailure?.(
|
|
1996
|
+
protocolFailure("UNKNOWN_MESSAGE", `unknown preview message "${envelope.type}"`)
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
const onMessage = (event) => {
|
|
2001
|
+
if (disposed || event.origin !== trustedPreviewOrigin) return;
|
|
2002
|
+
const expectedTarget = currentTarget();
|
|
2003
|
+
if (event.source !== expectedTarget) return;
|
|
2004
|
+
const parsed = parseEnvelope(event.data, PREVIEW_SOURCE, security, inbound, now());
|
|
2005
|
+
if ("code" in parsed) {
|
|
2006
|
+
onProtocolFailure?.(parsed);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
if (parsed.duplicate) return;
|
|
2010
|
+
if (parsed.envelope.type === "editor.protocol.failure") {
|
|
2011
|
+
process(parsed.envelope);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
if (parsed.envelope.type === "editor.ready") {
|
|
2015
|
+
const readyDocumentPayload = parseReadyDocument(
|
|
2016
|
+
parsed.envelope.payload,
|
|
2017
|
+
expectedManifest,
|
|
2018
|
+
submittedInstances
|
|
2019
|
+
);
|
|
2020
|
+
if (readyDocumentPayload === null) {
|
|
2021
|
+
onProtocolFailure?.(protocolFailure("UNKNOWN_MESSAGE", "ready payload is invalid"));
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
ready = true;
|
|
2025
|
+
onReady?.(readyDocumentPayload);
|
|
2026
|
+
while (buffered.length > 0) {
|
|
2027
|
+
const envelope = buffered.shift();
|
|
2028
|
+
if (envelope !== void 0) process(envelope);
|
|
2029
|
+
}
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (!ready) {
|
|
2033
|
+
if (buffered.length >= HYDROGEN_EDITOR_BRIDGE_LIMITS.maxBufferedMessages) {
|
|
2034
|
+
onProtocolFailure?.(
|
|
2035
|
+
protocolFailure(
|
|
2036
|
+
"BUFFER_LIMIT_EXCEEDED",
|
|
2037
|
+
"pre-ready Hydrogen preview message buffer is full"
|
|
2038
|
+
)
|
|
2039
|
+
);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
buffered.push(parsed.envelope);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
process(parsed.envelope);
|
|
2046
|
+
};
|
|
2047
|
+
transport.addEventListener("message", onMessage);
|
|
2048
|
+
return {
|
|
2049
|
+
requestReady: () => post("editor.ready.request"),
|
|
2050
|
+
hoverInstance: (instanceId) => {
|
|
2051
|
+
if (instanceId !== null && !isOpaqueId(instanceId)) return;
|
|
2052
|
+
post("instance.hover", { instanceId });
|
|
2053
|
+
},
|
|
2054
|
+
selectInstance: (instanceId) => {
|
|
2055
|
+
if (instanceId !== null && !isOpaqueId(instanceId)) return;
|
|
2056
|
+
post("instance.select", { instanceId });
|
|
2057
|
+
},
|
|
2058
|
+
applyOperation: (operation) => {
|
|
2059
|
+
const parsed = parseOperation(operation);
|
|
2060
|
+
if (parsed === null) {
|
|
2061
|
+
throw new HydrogenEditorConfigurationError("operation does not satisfy the bridge contract");
|
|
2062
|
+
}
|
|
2063
|
+
if (parsed.type === "slot.insert") {
|
|
2064
|
+
submittedInstances.set(parsed.instance.instanceId, parsed.instance);
|
|
2065
|
+
}
|
|
2066
|
+
post("preview.operation.apply", parsed);
|
|
2067
|
+
},
|
|
2068
|
+
dispose: () => {
|
|
2069
|
+
if (disposed) return;
|
|
2070
|
+
disposed = true;
|
|
2071
|
+
buffered.length = 0;
|
|
2072
|
+
transport.removeEventListener("message", onMessage);
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
function resolveHydrogenEditorStyles(manifest, instance, viewportWidth) {
|
|
2077
|
+
const component = manifest.components.find(
|
|
2078
|
+
(candidate) => candidate.componentId === instance.componentId
|
|
2079
|
+
);
|
|
2080
|
+
if (component === void 0) return {};
|
|
2081
|
+
const matchingBreakpoints = viewportWidth === void 0 ? [] : [...manifest.breakpoints].filter((breakpoint) => breakpoint.minWidth <= viewportWidth).sort(
|
|
2082
|
+
(left, right) => left.minWidth - right.minWidth || left.breakpointId.localeCompare(right.breakpointId)
|
|
2083
|
+
);
|
|
2084
|
+
const variant = component.variants.find((candidate) => candidate.variantId === instance.variantId);
|
|
2085
|
+
const tokens = {
|
|
2086
|
+
...variant?.styles.tokens,
|
|
2087
|
+
...instance.styles.base.tokens
|
|
2088
|
+
};
|
|
2089
|
+
const layout = {
|
|
2090
|
+
...variant?.styles.layout,
|
|
2091
|
+
...instance.styles.base.layout
|
|
2092
|
+
};
|
|
2093
|
+
for (const breakpoint of matchingBreakpoints) {
|
|
2094
|
+
const override = instance.styles.overrides.find(
|
|
2095
|
+
(candidate) => candidate.breakpointId === breakpoint.breakpointId
|
|
2096
|
+
);
|
|
2097
|
+
Object.assign(tokens, override?.values.tokens);
|
|
2098
|
+
Object.assign(layout, override?.values.layout);
|
|
2099
|
+
}
|
|
2100
|
+
const styles = {};
|
|
2101
|
+
for (const token of component.styles.tokens) {
|
|
2102
|
+
const value = tokens[token.tokenId];
|
|
2103
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2104
|
+
styles[token.cssVariable] = value;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
for (const control of component.styles.layout) {
|
|
2108
|
+
const value = layout[control.controlId];
|
|
2109
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
2110
|
+
styles[control.cssProperty] = value;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return styles;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
export { HYDROGEN_EDITOR_BRIDGE_LIMITS, HYDROGEN_EDITOR_PROTOCOL_VERSION, HydrogenEditorConfigurationError, createHydrogenEditorHostBridge, createHydrogenEditorPreviewRuntime, createHydrogenEditorSourceProjection, parseHydrogenEditorCanvasIntent, resolveHydrogenEditorStyles };
|
|
2117
|
+
//# sourceMappingURL=chunk-XXNCFWXT.js.map
|
|
2118
|
+
//# sourceMappingURL=chunk-XXNCFWXT.js.map
|