@tailorkit/react 0.1.0-beta.18 → 0.1.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.ts +1 -0
- package/dist/components/slot.d.ts +42 -0
- package/dist/hooks/use-apps.d.ts +5 -1
- package/dist/hooks/{use-view.d.ts → use-register-view.d.ts} +2 -2
- package/dist/hooks/use-slot-instances.d.ts +13 -0
- package/dist/hooks/use-views.d.ts +9 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +937 -760
- package/dist/preview-manager.d.ts +1 -1
- package/dist/slot-view.d.ts +22 -0
- package/dist/store.d.ts +7 -5
- package/dist/tailorkit.d.ts +23 -24
- package/package.json +5 -5
- package/dist/components/app-view.d.ts +0 -3
package/dist/index.js
CHANGED
|
@@ -1,522 +1,170 @@
|
|
|
1
|
-
// src/components/
|
|
2
|
-
import { createSessionProvider } from "@tailorkit/app/client";
|
|
3
|
-
import { useCallback as
|
|
4
|
-
import { isViewAncestor as isViewAncestor2 } from "@tailorkit/core/views";
|
|
1
|
+
// src/components/slot.tsx
|
|
2
|
+
import { createSessionProvider as createSessionProvider2 } from "@tailorkit/app/client";
|
|
3
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useId as useId2, useMemo as useMemo4, useRef as useRef4, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
5
4
|
|
|
6
|
-
// src/
|
|
7
|
-
import {
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)}. TailorKit selected "${selected.view}" by mount order. Only one route at a hierarchy depth should call useView.`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
snapshot = {
|
|
37
|
-
view: selected.view,
|
|
38
|
-
layers: ordered.filter((entry) => isViewAncestor(entry.view, selected.view)).toReversed().map((entry) => ({ path: entry.view, context: entry.context, status: entry.status }))
|
|
39
|
-
};
|
|
40
|
-
} else {
|
|
41
|
-
snapshot = null;
|
|
42
|
-
}
|
|
43
|
-
for (const listener of listeners) {
|
|
44
|
-
listener();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
};
|
|
5
|
+
// src/hooks/use-stable-context.ts
|
|
6
|
+
import { useRef } from "react";
|
|
7
|
+
function useStableContext(value) {
|
|
8
|
+
const key = JSON.stringify(value);
|
|
9
|
+
const ref = useRef({ key, value });
|
|
10
|
+
if (ref.current.key !== key) {
|
|
11
|
+
ref.current = { key, value };
|
|
12
|
+
}
|
|
13
|
+
return ref.current.value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/hooks/use-slot-instances.ts
|
|
17
|
+
import { createClient, createSessionProvider, reference } from "@tailorkit/app/client";
|
|
18
|
+
|
|
19
|
+
// src/slot-view.ts
|
|
20
|
+
import { composeViewContext, getViewHierarchy } from "@tailorkit/core/views";
|
|
21
|
+
function selectSlotView(views, slot, activePath, schema) {
|
|
22
|
+
const supported = schema.slots[slot]?.views ?? [];
|
|
23
|
+
const path = getViewHierarchy(activePath).find(
|
|
24
|
+
(path2) => supported.includes(path2) && views.some((view2) => view2.slot === slot && view2.path === path2)
|
|
25
|
+
);
|
|
26
|
+
const view = views.find((view2) => view2.slot === slot && view2.path === path);
|
|
27
|
+
return !view || view.disabled ? null : view;
|
|
28
|
+
}
|
|
29
|
+
function resolveSlotView(views, slot, activeView, schema) {
|
|
30
|
+
const view = selectSlotView(views, slot, activeView.view, schema);
|
|
31
|
+
if (!view) return null;
|
|
48
32
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
register(entry) {
|
|
57
|
-
entries.set(entry.id, { ...entry, order: entries.get(entry.id)?.order ?? nextOrder++ });
|
|
58
|
-
publish();
|
|
59
|
-
},
|
|
60
|
-
unregister(id) {
|
|
61
|
-
entries.delete(id);
|
|
62
|
-
publish();
|
|
63
|
-
}
|
|
33
|
+
...composeViewContext(view.path, {
|
|
34
|
+
layers: activeView.layers,
|
|
35
|
+
declaredViews: Object.keys(schema.views)
|
|
36
|
+
}),
|
|
37
|
+
instances: Boolean(view.instances)
|
|
64
38
|
};
|
|
65
39
|
}
|
|
66
40
|
|
|
67
|
-
// src/
|
|
68
|
-
import {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} from "
|
|
72
|
-
var
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
function bytesFromBase64(value) {
|
|
78
|
-
const raw = atob(value);
|
|
79
|
-
const bytes = new Uint8Array(raw.length);
|
|
80
|
-
for (let index = 0; index < raw.length; index++) {
|
|
81
|
-
bytes[index] = raw.codePointAt(index) ?? 0;
|
|
41
|
+
// src/hooks/use-slot-instances.ts
|
|
42
|
+
import { useCallback, useEffect, useMemo, useRef as useRef2, useState, useSyncExternalStore } from "react";
|
|
43
|
+
|
|
44
|
+
// src/components/context.ts
|
|
45
|
+
import { createContext, useContext } from "react";
|
|
46
|
+
var TailorRootContext = createContext(null);
|
|
47
|
+
function useTailorRootContext(component, expectedClient) {
|
|
48
|
+
const context = useContext(TailorRootContext);
|
|
49
|
+
if (!context) {
|
|
50
|
+
throw new Error(`${component} must be rendered inside Root.`);
|
|
82
51
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
for (const [fileIndex, file] of candidate.manifest.files.entries()) {
|
|
88
|
-
const parts = candidate.files[fileIndex];
|
|
89
|
-
if (!parts || parts.length !== file.chunks || parts.some((part) => part === null)) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
const bytes = new Uint8Array(file.size);
|
|
93
|
-
let offset = 0;
|
|
94
|
-
for (const part of parts) {
|
|
95
|
-
if (!part) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
bytes.set(part, offset);
|
|
99
|
-
offset += part.length;
|
|
100
|
-
}
|
|
101
|
-
if (offset !== file.size) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
105
|
-
if (digest !== file.sha256) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
if (file.path === "client.js") {
|
|
109
|
-
source = new TextDecoder().decode(bytes);
|
|
110
|
-
}
|
|
52
|
+
if (expectedClient && context.client !== expectedClient) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${component} was created for a different TailorKit client than the one passed to Root.`
|
|
55
|
+
);
|
|
111
56
|
}
|
|
112
|
-
return
|
|
57
|
+
return context;
|
|
113
58
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!candidate || candidate.revision !== event.revision) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (event.type === "chunk") {
|
|
160
|
-
const file = candidate.manifest.files[event.fileIndex];
|
|
161
|
-
const slots = candidate.files[event.fileIndex];
|
|
162
|
-
if (!file || !slots || event.chunkIndex < 0 || event.chunkIndex >= slots.length || slots[event.chunkIndex]) {
|
|
163
|
-
entry.candidate = null;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const bytes = bytesFromBase64(event.base64);
|
|
167
|
-
if (bytes.length !== Math.min(256 * 1024, file.size - event.chunkIndex * 256 * 1024)) {
|
|
168
|
-
entry.candidate = null;
|
|
169
|
-
return;
|
|
59
|
+
|
|
60
|
+
// src/hooks/use-slot-instances.ts
|
|
61
|
+
var resolveInstances = reference("_tailorkit.instances.resolve", "action");
|
|
62
|
+
function useSlotInstances({ app, slot }) {
|
|
63
|
+
const { store } = useTailorRootContext("useSlotInstances");
|
|
64
|
+
const activeView = useSyncExternalStore(
|
|
65
|
+
store.views.subscribe,
|
|
66
|
+
store.views.getSnapshot,
|
|
67
|
+
store.views.getSnapshot
|
|
68
|
+
);
|
|
69
|
+
const request = useStableContext({
|
|
70
|
+
baseUrl: store.baseUrl.toString(),
|
|
71
|
+
appId: app.id,
|
|
72
|
+
deploymentId: app.currentDeployment?.id,
|
|
73
|
+
views: app.views ?? [],
|
|
74
|
+
slot,
|
|
75
|
+
activeView
|
|
76
|
+
});
|
|
77
|
+
const getSession = useMemo(
|
|
78
|
+
() => createSessionProvider({ baseUrl: store.baseUrl, appId: app.id }),
|
|
79
|
+
[store, app.id, app.currentDeployment?.id]
|
|
80
|
+
);
|
|
81
|
+
const [snapshot, setSnapshot] = useState({
|
|
82
|
+
request: null,
|
|
83
|
+
data: void 0,
|
|
84
|
+
error: null,
|
|
85
|
+
status: "idle"
|
|
86
|
+
});
|
|
87
|
+
const generation = useRef2(0);
|
|
88
|
+
const backend = useRef2(null);
|
|
89
|
+
const refetch = useCallback(async () => {
|
|
90
|
+
const id = ++generation.current;
|
|
91
|
+
backend.current?.close();
|
|
92
|
+
backend.current = null;
|
|
93
|
+
const publish = (status2, data, error = null) => {
|
|
94
|
+
if (id === generation.current) {
|
|
95
|
+
setSnapshot({
|
|
96
|
+
request,
|
|
97
|
+
status: status2,
|
|
98
|
+
data,
|
|
99
|
+
error
|
|
100
|
+
});
|
|
170
101
|
}
|
|
171
|
-
|
|
102
|
+
};
|
|
103
|
+
if (!request.activeView) {
|
|
104
|
+
publish("idle");
|
|
172
105
|
return;
|
|
173
106
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
if (source !== null && entry.candidate === candidate && candidate.revision > entry.snapshot.revision) {
|
|
177
|
-
entry.snapshot = { revision: candidate.revision, source };
|
|
178
|
-
notify(entry);
|
|
179
|
-
}
|
|
180
|
-
if (entry.candidate === candidate) {
|
|
181
|
-
entry.candidate = null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const connect = async (entry) => {
|
|
186
|
-
if (!entry.listeners.size || !entry.app.preview || entry.socket || entry.connecting) {
|
|
107
|
+
if (!request.views.some((view) => view.slot === slot && view.instances && !view.disabled)) {
|
|
108
|
+
publish("ready", []);
|
|
187
109
|
return;
|
|
188
110
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
let metadata = entry.metadata && viewerTokenExpiresAt(entry.metadata) > Math.max(Date.now(), viewerTokenExpiresAt(appMetadata)) ? entry.metadata : appMetadata;
|
|
111
|
+
publish("loading");
|
|
112
|
+
let client;
|
|
192
113
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
114
|
+
await store.fetchMeta({ force: store.getMetaSnapshot().status === "error" });
|
|
115
|
+
if (id !== generation.current) return;
|
|
116
|
+
const meta = store.getMetaSnapshot();
|
|
117
|
+
if (meta.error) throw meta.error;
|
|
118
|
+
if (!meta.schema) throw new Error("TailorKit metadata is unavailable.");
|
|
119
|
+
const resolved = resolveSlotView(request.views, slot, request.activeView, meta.schema);
|
|
120
|
+
if (!resolved?.instances) {
|
|
121
|
+
publish("ready", []);
|
|
201
122
|
return;
|
|
202
123
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const socket = new WebSocket(metadata.websocketUrl, metadata.token);
|
|
210
|
-
entry.socket = socket;
|
|
211
|
-
entry.connecting = false;
|
|
212
|
-
socket.addEventListener("open", () => {
|
|
213
|
-
entry.delay = 1e3;
|
|
214
|
-
const client = createPreviewWebSocketClient(socket);
|
|
215
|
-
void (async () => {
|
|
216
|
-
try {
|
|
217
|
-
const stream = await client.subscribe();
|
|
218
|
-
for await (const event of stream) {
|
|
219
|
-
await handleEvent(entry, event);
|
|
220
|
-
}
|
|
221
|
-
} catch {
|
|
222
|
-
} finally {
|
|
223
|
-
socket.close();
|
|
224
|
-
}
|
|
225
|
-
})();
|
|
226
|
-
});
|
|
227
|
-
socket.addEventListener("error", () => socket.close());
|
|
228
|
-
socket.addEventListener("close", () => {
|
|
229
|
-
if (entry.socket !== socket) {
|
|
124
|
+
if (resolved.status !== "ready") {
|
|
125
|
+
if (resolved.status === "error")
|
|
126
|
+
throw new Error(`Context for view "${resolved.view}" is unavailable.`);
|
|
127
|
+
publish("loading");
|
|
230
128
|
return;
|
|
231
129
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}, entry.delay);
|
|
239
|
-
entry.delay = Math.min(entry.delay * 2, 3e4);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
};
|
|
243
|
-
return {
|
|
244
|
-
getSnapshot: (sessionId) => entries.get(sessionId)?.snapshot ?? empty,
|
|
245
|
-
updateApp: (app) => {
|
|
246
|
-
const sessionId = app.preview?.sessionId;
|
|
247
|
-
const entry = sessionId && entries.get(sessionId);
|
|
248
|
-
if (entry) {
|
|
249
|
-
entry.app = app;
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
subscribe: (app, listener) => {
|
|
253
|
-
const sessionId = app.preview?.sessionId;
|
|
254
|
-
if (!sessionId) {
|
|
255
|
-
return () => {
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
let entry = entries.get(sessionId);
|
|
259
|
-
if (!entry) {
|
|
260
|
-
entry = {
|
|
261
|
-
app,
|
|
262
|
-
metadata: null,
|
|
263
|
-
candidate: null,
|
|
264
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
265
|
-
snapshot: empty,
|
|
266
|
-
socket: null,
|
|
267
|
-
connecting: false,
|
|
268
|
-
reconnect: null,
|
|
269
|
-
delay: 1e3
|
|
270
|
-
};
|
|
271
|
-
entries.set(sessionId, entry);
|
|
272
|
-
}
|
|
273
|
-
entry.app = app;
|
|
274
|
-
entry.listeners.add(listener);
|
|
275
|
-
void connect(entry);
|
|
276
|
-
return () => {
|
|
277
|
-
entry?.listeners.delete(listener);
|
|
278
|
-
if (entry && !entry.listeners.size) {
|
|
279
|
-
close(entry);
|
|
280
|
-
entries.delete(sessionId);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
},
|
|
284
|
-
dispose: () => {
|
|
285
|
-
for (const entry of entries.values()) {
|
|
286
|
-
close(entry);
|
|
287
|
-
}
|
|
288
|
-
entries.clear();
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// src/scope-query.ts
|
|
294
|
-
function appendScopeSelection(url, scopes) {
|
|
295
|
-
if (scopes === void 0) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (scopes.length === 0) {
|
|
299
|
-
url.searchParams.append("scopes", "");
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
for (const scope of scopes) {
|
|
303
|
-
url.searchParams.append("scopes", scope);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
function normalizeScopeSelection(scopes) {
|
|
307
|
-
if (scopes === void 0) {
|
|
308
|
-
return { key: "*" };
|
|
309
|
-
}
|
|
310
|
-
const names = [...new Set(scopes)].sort();
|
|
311
|
-
return { key: JSON.stringify(names), scopes: names };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// src/store.ts
|
|
315
|
-
function createTailorKitStore(baseUrlInput, initialApps) {
|
|
316
|
-
const baseUrl = toBaseUrl(baseUrlInput);
|
|
317
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
318
|
-
let providedApps = initialApps;
|
|
319
|
-
let unrequestedSnapshot = {
|
|
320
|
-
apps: initialApps ?? [],
|
|
321
|
-
error: null,
|
|
322
|
-
status: initialApps === void 0 ? "idle" : "ready"
|
|
323
|
-
};
|
|
324
|
-
const appsEntries = /* @__PURE__ */ new Map();
|
|
325
|
-
let metaSnapshot = {
|
|
326
|
-
assetsBaseUrl: null,
|
|
327
|
-
error: null,
|
|
328
|
-
schema: null,
|
|
329
|
-
status: "idle"
|
|
330
|
-
};
|
|
331
|
-
let fetchMetaPromise = null;
|
|
332
|
-
const getAppsEntry = (scopes) => {
|
|
333
|
-
const selection = normalizeScopeSelection(scopes);
|
|
334
|
-
let entry = appsEntries.get(selection.key);
|
|
335
|
-
if (!entry) {
|
|
336
|
-
entry = {
|
|
337
|
-
scopes: selection.scopes,
|
|
338
|
-
snapshot: unrequestedSnapshot,
|
|
339
|
-
promise: null,
|
|
340
|
-
requestId: 0,
|
|
341
|
-
requested: false,
|
|
342
|
-
subscribers: 0
|
|
343
|
-
};
|
|
344
|
-
appsEntries.set(selection.key, entry);
|
|
345
|
-
}
|
|
346
|
-
return entry;
|
|
347
|
-
};
|
|
348
|
-
const emit = () => {
|
|
349
|
-
for (const listener of listeners) {
|
|
350
|
-
listener();
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
const store = {
|
|
354
|
-
baseUrl,
|
|
355
|
-
views: createViewRegistry(),
|
|
356
|
-
setProvidedApps(apps) {
|
|
357
|
-
if (providedApps === apps) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
providedApps = apps;
|
|
361
|
-
unrequestedSnapshot = {
|
|
362
|
-
apps: apps ?? [],
|
|
363
|
-
error: null,
|
|
364
|
-
status: apps === void 0 ? "idle" : "ready"
|
|
365
|
-
};
|
|
366
|
-
for (const entry of appsEntries.values()) {
|
|
367
|
-
entry.requestId += 1;
|
|
368
|
-
entry.promise = null;
|
|
369
|
-
entry.snapshot = unrequestedSnapshot;
|
|
370
|
-
}
|
|
371
|
-
emit();
|
|
372
|
-
if (apps === void 0) {
|
|
373
|
-
for (const entry of appsEntries.values()) {
|
|
374
|
-
if (entry.requested && entry.subscribers > 0) {
|
|
375
|
-
void store.fetchApps({ scopes: entry.scopes });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
fetchApps: (options = {}) => {
|
|
381
|
-
const entry = getAppsEntry(options.scopes);
|
|
382
|
-
entry.requested = true;
|
|
383
|
-
if (providedApps !== void 0) {
|
|
384
|
-
return Promise.resolve();
|
|
385
|
-
}
|
|
386
|
-
if (entry.promise && !options.force) {
|
|
387
|
-
return entry.promise;
|
|
388
|
-
}
|
|
389
|
-
entry.snapshot = { ...entry.snapshot, status: "loading" };
|
|
390
|
-
emit();
|
|
391
|
-
const requestId = ++entry.requestId;
|
|
392
|
-
const appsUrl = new URL("apps", baseUrl);
|
|
393
|
-
appendScopeSelection(appsUrl, entry.scopes);
|
|
394
|
-
entry.promise = fetch(appsUrl).then(async (response) => {
|
|
395
|
-
if (!response.ok) {
|
|
396
|
-
throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
|
|
397
|
-
}
|
|
398
|
-
const apps = await response.json();
|
|
399
|
-
if (requestId !== entry.requestId) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
entry.snapshot = {
|
|
403
|
-
apps,
|
|
404
|
-
error: null,
|
|
405
|
-
status: "ready"
|
|
406
|
-
};
|
|
407
|
-
emit();
|
|
408
|
-
}).catch((error) => {
|
|
409
|
-
if (requestId !== entry.requestId) {
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
entry.snapshot = {
|
|
413
|
-
...entry.snapshot,
|
|
414
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
415
|
-
status: "error"
|
|
416
|
-
};
|
|
417
|
-
emit();
|
|
418
|
-
});
|
|
419
|
-
return entry.promise;
|
|
420
|
-
},
|
|
421
|
-
fetchMeta: () => {
|
|
422
|
-
if (fetchMetaPromise) {
|
|
423
|
-
return fetchMetaPromise;
|
|
424
|
-
}
|
|
425
|
-
metaSnapshot = { ...metaSnapshot, status: "loading" };
|
|
426
|
-
emit();
|
|
427
|
-
fetchMetaPromise = fetch(new URL("meta", baseUrl)).then(async (response) => {
|
|
428
|
-
if (!response.ok) {
|
|
429
|
-
throw new Error(`Unable to fetch TailorKit metadata from ${baseUrl.toString()}.`);
|
|
430
|
-
}
|
|
431
|
-
const meta = await response.json();
|
|
432
|
-
metaSnapshot = {
|
|
433
|
-
assetsBaseUrl: meta.assetsBaseUrl ?? null,
|
|
434
|
-
error: null,
|
|
435
|
-
schema: meta.schema,
|
|
436
|
-
status: "ready"
|
|
437
|
-
};
|
|
438
|
-
emit();
|
|
439
|
-
}).catch((error) => {
|
|
440
|
-
metaSnapshot = {
|
|
441
|
-
...metaSnapshot,
|
|
442
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
443
|
-
status: "error"
|
|
444
|
-
};
|
|
445
|
-
emit();
|
|
130
|
+
client = createClient({ getSession });
|
|
131
|
+
backend.current = client;
|
|
132
|
+
const instances = await client.action(resolveInstances, {
|
|
133
|
+
slot,
|
|
134
|
+
path: resolved.view,
|
|
135
|
+
context: resolved.context
|
|
446
136
|
});
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return () => {
|
|
454
|
-
listeners.delete(listener);
|
|
455
|
-
};
|
|
456
|
-
},
|
|
457
|
-
subscribeApps: (scopes, listener) => {
|
|
458
|
-
const selection = normalizeScopeSelection(scopes);
|
|
459
|
-
const entry = getAppsEntry(selection.scopes);
|
|
460
|
-
entry.subscribers += 1;
|
|
461
|
-
listeners.add(listener);
|
|
462
|
-
return () => {
|
|
463
|
-
listeners.delete(listener);
|
|
464
|
-
entry.subscribers -= 1;
|
|
465
|
-
if (entry.subscribers === 0) {
|
|
466
|
-
entry.requestId += 1;
|
|
467
|
-
appsEntries.delete(selection.key);
|
|
468
|
-
}
|
|
469
|
-
};
|
|
137
|
+
publish("ready", instances);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
publish("error", void 0, error instanceof Error ? error : new Error(String(error)));
|
|
140
|
+
} finally {
|
|
141
|
+
client?.close();
|
|
142
|
+
if (backend.current === client) backend.current = null;
|
|
470
143
|
}
|
|
471
|
-
};
|
|
144
|
+
}, [request, store, slot, getSession]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
void refetch();
|
|
147
|
+
return () => {
|
|
148
|
+
generation.current += 1;
|
|
149
|
+
backend.current?.close();
|
|
150
|
+
backend.current = null;
|
|
151
|
+
};
|
|
152
|
+
}, [refetch]);
|
|
153
|
+
const status = snapshot.request === request ? snapshot.status : activeView ? "loading" : "idle";
|
|
472
154
|
return {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
155
|
+
data: snapshot.request === request ? snapshot.data : void 0,
|
|
156
|
+
error: snapshot.request === request ? snapshot.error : null,
|
|
157
|
+
status,
|
|
158
|
+
isPending: status === "idle" || status === "loading",
|
|
159
|
+
isLoading: status === "loading",
|
|
160
|
+
isSuccess: status === "ready",
|
|
161
|
+
isError: status === "error",
|
|
162
|
+
refetch
|
|
481
163
|
};
|
|
482
164
|
}
|
|
483
|
-
function toBaseUrl(value) {
|
|
484
|
-
const url = value instanceof URL ? new URL(value) : new URL(value);
|
|
485
|
-
if (!url.pathname.endsWith("/")) {
|
|
486
|
-
url.pathname = `${url.pathname}/`;
|
|
487
|
-
}
|
|
488
|
-
return url;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// src/hooks/use-stable-context.ts
|
|
492
|
-
import { useRef } from "react";
|
|
493
|
-
function useStableContext(value) {
|
|
494
|
-
const key = JSON.stringify(value);
|
|
495
|
-
const ref = useRef({ key, value });
|
|
496
|
-
if (ref.current.key !== key) {
|
|
497
|
-
ref.current = { key, value };
|
|
498
|
-
}
|
|
499
|
-
return ref.current.value;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// src/components/context.ts
|
|
503
|
-
import { createContext, useContext } from "react";
|
|
504
|
-
var TailorRootContext = createContext(null);
|
|
505
|
-
function useTailorRootContext(component, expectedClient) {
|
|
506
|
-
const context = useContext(TailorRootContext);
|
|
507
|
-
if (!context) {
|
|
508
|
-
throw new Error(`${component} must be rendered inside Root.`);
|
|
509
|
-
}
|
|
510
|
-
if (expectedClient && context.client !== expectedClient) {
|
|
511
|
-
throw new Error(
|
|
512
|
-
`${component} was created for a different TailorKit client than the one passed to Root.`
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
return context;
|
|
516
|
-
}
|
|
517
165
|
|
|
518
166
|
// src/primitives.tsx
|
|
519
|
-
import { createContext as createContext2, useContext as useContext2, useId, useMemo } from "react";
|
|
167
|
+
import { createContext as createContext2, useContext as useContext2, useId, useMemo as useMemo2 } from "react";
|
|
520
168
|
import { resolveTheme } from "@tailorkit/core/primitives/theme";
|
|
521
169
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
522
170
|
var PrimitiveThemeContext = createContext2(null);
|
|
@@ -683,7 +331,7 @@ var buildThemeCss = (viewId, theme) => {
|
|
|
683
331
|
};
|
|
684
332
|
var usePrimitiveNodeId = () => {
|
|
685
333
|
const id = useId();
|
|
686
|
-
return
|
|
334
|
+
return useMemo2(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
|
|
687
335
|
};
|
|
688
336
|
var Primitive = ({
|
|
689
337
|
children,
|
|
@@ -719,13 +367,13 @@ import {
|
|
|
719
367
|
Fragment,
|
|
720
368
|
createElement,
|
|
721
369
|
memo,
|
|
722
|
-
useCallback,
|
|
370
|
+
useCallback as useCallback2,
|
|
723
371
|
useContext as useContext3,
|
|
724
|
-
useEffect,
|
|
725
|
-
useMemo as
|
|
726
|
-
useRef as
|
|
727
|
-
useState,
|
|
728
|
-
useSyncExternalStore
|
|
372
|
+
useEffect as useEffect2,
|
|
373
|
+
useMemo as useMemo3,
|
|
374
|
+
useRef as useRef3,
|
|
375
|
+
useState as useState2,
|
|
376
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
729
377
|
} from "react";
|
|
730
378
|
import { createIframeUiHost } from "@tailorkit/sandbox/host";
|
|
731
379
|
|
|
@@ -855,195 +503,687 @@ var RemoteErrorBoundary = class extends Component {
|
|
|
855
503
|
if (this.state.error) {
|
|
856
504
|
return createElement("div", null, formatError(this.state.error));
|
|
857
505
|
}
|
|
858
|
-
return this.props.children;
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
function RemoteViewHost({
|
|
862
|
-
appUrl,
|
|
863
|
-
sourceText,
|
|
864
|
-
getBackendSession,
|
|
865
|
-
components: components2,
|
|
866
|
-
createIframe,
|
|
867
|
-
props
|
|
868
|
-
}) {
|
|
869
|
-
const appKey = appUrl.toString();
|
|
870
|
-
const storeRef =
|
|
871
|
-
if (storeRef.current === null) {
|
|
872
|
-
storeRef.current = new NodeStore();
|
|
873
|
-
}
|
|
874
|
-
const store = storeRef.current;
|
|
875
|
-
const appKeyRef =
|
|
876
|
-
if (appKeyRef.current !== appKey) {
|
|
877
|
-
appKeyRef.current = appKey;
|
|
878
|
-
store.clear();
|
|
879
|
-
}
|
|
880
|
-
const hostRef =
|
|
881
|
-
const propsRef =
|
|
882
|
-
|
|
883
|
-
propsRef.current = props;
|
|
884
|
-
hostRef.current?.setProps(props);
|
|
885
|
-
}, [props]);
|
|
886
|
-
const dispatchRef =
|
|
887
|
-
const [error, setError] =
|
|
888
|
-
const [status, setStatus] =
|
|
889
|
-
const ctxRef =
|
|
890
|
-
components: components2,
|
|
891
|
-
dispatch: (payload) => dispatchRef.current?.(payload),
|
|
892
|
-
store
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
ctxRef.current.components = components2;
|
|
896
|
-
}, [components2]);
|
|
897
|
-
const ui =
|
|
898
|
-
const ctx = ctxRef.current;
|
|
899
|
-
return createElement(
|
|
900
|
-
RemoteUIContext.Provider,
|
|
901
|
-
{ value: ctx },
|
|
902
|
-
createElement(
|
|
903
|
-
RemoteErrorBoundary,
|
|
904
|
-
{ key: appKey },
|
|
905
|
-
createElement(RemoteRoot, { store: ctx.store })
|
|
906
|
-
)
|
|
907
|
-
);
|
|
908
|
-
}, [appKey]);
|
|
909
|
-
|
|
910
|
-
const host = createIframeUiHost(appUrl, {
|
|
911
|
-
createIframe,
|
|
912
|
-
sourceText,
|
|
913
|
-
getBackendSession,
|
|
914
|
-
onError: (error2) => {
|
|
915
|
-
console.error("TailorKit remote app failed", error2);
|
|
916
|
-
setError(error2);
|
|
917
|
-
setStatus("error");
|
|
506
|
+
return this.props.children;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
function RemoteViewHost({
|
|
510
|
+
appUrl,
|
|
511
|
+
sourceText,
|
|
512
|
+
getBackendSession,
|
|
513
|
+
components: components2,
|
|
514
|
+
createIframe,
|
|
515
|
+
props
|
|
516
|
+
}) {
|
|
517
|
+
const appKey = appUrl.toString();
|
|
518
|
+
const storeRef = useRef3(null);
|
|
519
|
+
if (storeRef.current === null) {
|
|
520
|
+
storeRef.current = new NodeStore();
|
|
521
|
+
}
|
|
522
|
+
const store = storeRef.current;
|
|
523
|
+
const appKeyRef = useRef3(appKey);
|
|
524
|
+
if (appKeyRef.current !== appKey) {
|
|
525
|
+
appKeyRef.current = appKey;
|
|
526
|
+
store.clear();
|
|
527
|
+
}
|
|
528
|
+
const hostRef = useRef3(null);
|
|
529
|
+
const propsRef = useRef3(props);
|
|
530
|
+
useEffect2(() => {
|
|
531
|
+
propsRef.current = props;
|
|
532
|
+
hostRef.current?.setProps(props);
|
|
533
|
+
}, [props]);
|
|
534
|
+
const dispatchRef = useRef3(null);
|
|
535
|
+
const [error, setError] = useState2(null);
|
|
536
|
+
const [status, setStatus] = useState2("starting");
|
|
537
|
+
const ctxRef = useRef3({
|
|
538
|
+
components: components2,
|
|
539
|
+
dispatch: (payload) => dispatchRef.current?.(payload),
|
|
540
|
+
store
|
|
541
|
+
});
|
|
542
|
+
useEffect2(() => {
|
|
543
|
+
ctxRef.current.components = components2;
|
|
544
|
+
}, [components2]);
|
|
545
|
+
const ui = useMemo3(() => {
|
|
546
|
+
const ctx = ctxRef.current;
|
|
547
|
+
return createElement(
|
|
548
|
+
RemoteUIContext.Provider,
|
|
549
|
+
{ value: ctx },
|
|
550
|
+
createElement(
|
|
551
|
+
RemoteErrorBoundary,
|
|
552
|
+
{ key: appKey },
|
|
553
|
+
createElement(RemoteRoot, { store: ctx.store })
|
|
554
|
+
)
|
|
555
|
+
);
|
|
556
|
+
}, [appKey]);
|
|
557
|
+
useEffect2(() => {
|
|
558
|
+
const host = createIframeUiHost(appUrl, {
|
|
559
|
+
createIframe,
|
|
560
|
+
sourceText,
|
|
561
|
+
getBackendSession,
|
|
562
|
+
onError: (error2) => {
|
|
563
|
+
console.error("TailorKit remote app failed", error2);
|
|
564
|
+
setError(error2);
|
|
565
|
+
setStatus("error");
|
|
566
|
+
},
|
|
567
|
+
props: propsRef.current
|
|
568
|
+
});
|
|
569
|
+
hostRef.current = host;
|
|
570
|
+
dispatchRef.current = (payload) => host.dispatch(payload);
|
|
571
|
+
const unsubscribe = host.subscribe(() => {
|
|
572
|
+
const tree = host.getSnapshot();
|
|
573
|
+
if (tree !== null) {
|
|
574
|
+
store.setSnapshot(tree);
|
|
575
|
+
setStatus("ready");
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
setStatus("starting");
|
|
579
|
+
setError(null);
|
|
580
|
+
host.mount();
|
|
581
|
+
return () => {
|
|
582
|
+
unsubscribe();
|
|
583
|
+
dispatchRef.current = null;
|
|
584
|
+
hostRef.current = null;
|
|
585
|
+
host.destroy();
|
|
586
|
+
};
|
|
587
|
+
}, [appUrl, createIframe, sourceText, getBackendSession, store]);
|
|
588
|
+
if (status === "error" && error) {
|
|
589
|
+
return createElement("div", null, formatError(error));
|
|
590
|
+
}
|
|
591
|
+
return ui;
|
|
592
|
+
}
|
|
593
|
+
function RemoteRoot({ store }) {
|
|
594
|
+
const subscribe = useCallback2((listener) => store.subscribeRoot(listener), [store]);
|
|
595
|
+
const getSnapshot = useCallback2(() => store.getRootId(), [store]);
|
|
596
|
+
const rootId = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
|
|
597
|
+
if (rootId === null) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
return /* @__PURE__ */ jsx2(RemoteView, { nodeId: rootId });
|
|
601
|
+
}
|
|
602
|
+
var RemoteElementView = memo(({ ctx, node }) => {
|
|
603
|
+
const { dispatch, components: components2 } = ctx;
|
|
604
|
+
const children = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
|
|
605
|
+
const component = components2[node.type];
|
|
606
|
+
if (component === void 0) {
|
|
607
|
+
throw new Error(`TailorKit component "${node.type}" is not registered.`);
|
|
608
|
+
}
|
|
609
|
+
const props = { ...node.props };
|
|
610
|
+
for (const binding of node.callbacks ?? []) {
|
|
611
|
+
props[binding.callback] = (...args) => {
|
|
612
|
+
dispatch({
|
|
613
|
+
data: {
|
|
614
|
+
args: args.slice(0, binding.inputCount),
|
|
615
|
+
event: binding.event,
|
|
616
|
+
nodeId: node.id
|
|
617
|
+
},
|
|
618
|
+
type: "dispatchCallback"
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
return createElement(component, props, ...children);
|
|
623
|
+
});
|
|
624
|
+
function RemoteViewComponent({ nodeId }) {
|
|
625
|
+
const ctx = useContext3(RemoteUIContext);
|
|
626
|
+
if (ctx === null) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
const { store } = ctx;
|
|
630
|
+
const subscribe = useCallback2(
|
|
631
|
+
(listener) => store.subscribe(nodeId, listener),
|
|
632
|
+
[store, nodeId]
|
|
633
|
+
);
|
|
634
|
+
const getSnapshot = useCallback2(() => store.getNode(nodeId), [store, nodeId]);
|
|
635
|
+
const node = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
|
|
636
|
+
if (node === null) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
if (node.kind === "text") {
|
|
640
|
+
return node.text;
|
|
641
|
+
}
|
|
642
|
+
const childElements = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
|
|
643
|
+
if (node.kind === "fragment") {
|
|
644
|
+
return createElement(Fragment, { key: node.id }, ...childElements);
|
|
645
|
+
}
|
|
646
|
+
return /* @__PURE__ */ jsx2(RemoteElementView, { node, ctx });
|
|
647
|
+
}
|
|
648
|
+
var RemoteView = memo(RemoteViewComponent);
|
|
649
|
+
|
|
650
|
+
// src/view-registry.ts
|
|
651
|
+
import { getViewDepth, isViewAncestor } from "@tailorkit/core/views";
|
|
652
|
+
function createViewRegistry() {
|
|
653
|
+
const entries = /* @__PURE__ */ new Map();
|
|
654
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
655
|
+
let snapshot = null;
|
|
656
|
+
let nextOrder = 0;
|
|
657
|
+
let scheduled = false;
|
|
658
|
+
const publish = () => {
|
|
659
|
+
if (scheduled) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
scheduled = true;
|
|
663
|
+
queueMicrotask(() => {
|
|
664
|
+
scheduled = false;
|
|
665
|
+
const ordered = [...entries.values()].toSorted(
|
|
666
|
+
(a, b) => getViewDepth(b.view) - getViewDepth(a.view) || b.order - a.order
|
|
667
|
+
);
|
|
668
|
+
const selected = ordered[0];
|
|
669
|
+
if (selected) {
|
|
670
|
+
const peers = ordered.filter(
|
|
671
|
+
(entry) => getViewDepth(entry.view) === getViewDepth(selected.view)
|
|
672
|
+
);
|
|
673
|
+
if (peers.length > 1) {
|
|
674
|
+
console.warn(
|
|
675
|
+
`TailorKit found multiple active views at the same hierarchy depth: ${peers.toReversed().map((entry) => `"${entry.view}"`).join(
|
|
676
|
+
", "
|
|
677
|
+
)}. TailorKit selected "${selected.view}" by mount order. Only one route at a hierarchy depth should call useRegisterView.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
snapshot = {
|
|
681
|
+
view: selected.view,
|
|
682
|
+
layers: ordered.filter((entry) => isViewAncestor(entry.view, selected.view)).toReversed().map((entry) => ({ path: entry.view, context: entry.context, status: entry.status }))
|
|
683
|
+
};
|
|
684
|
+
} else {
|
|
685
|
+
snapshot = null;
|
|
686
|
+
}
|
|
687
|
+
for (const listener of listeners) {
|
|
688
|
+
listener();
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
};
|
|
692
|
+
return {
|
|
693
|
+
getSnapshot: () => snapshot,
|
|
694
|
+
subscribe(listener) {
|
|
695
|
+
listeners.add(listener);
|
|
696
|
+
return () => {
|
|
697
|
+
listeners.delete(listener);
|
|
698
|
+
};
|
|
699
|
+
},
|
|
700
|
+
register(entry) {
|
|
701
|
+
entries.set(entry.id, { ...entry, order: entries.get(entry.id)?.order ?? nextOrder++ });
|
|
702
|
+
publish();
|
|
703
|
+
},
|
|
704
|
+
unregister(id) {
|
|
705
|
+
entries.delete(id);
|
|
706
|
+
publish();
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/preview-manager.ts
|
|
712
|
+
import {
|
|
713
|
+
createPreviewWebSocketClient,
|
|
714
|
+
previewMetadataSchema
|
|
715
|
+
} from "@tailorkit/client-platform/preview";
|
|
716
|
+
var empty = { revision: 0, source: null };
|
|
717
|
+
function viewerTokenExpiresAt(metadata) {
|
|
718
|
+
const expiresAt = Number(metadata.token.split(".", 1)[0]);
|
|
719
|
+
return Number.isSafeInteger(expiresAt) ? expiresAt : 0;
|
|
720
|
+
}
|
|
721
|
+
function bytesFromBase64(value) {
|
|
722
|
+
const raw = atob(value);
|
|
723
|
+
const bytes = new Uint8Array(raw.length);
|
|
724
|
+
for (let index = 0; index < raw.length; index++) {
|
|
725
|
+
bytes[index] = raw.codePointAt(index) ?? 0;
|
|
726
|
+
}
|
|
727
|
+
return bytes;
|
|
728
|
+
}
|
|
729
|
+
async function verify(candidate) {
|
|
730
|
+
let source = null;
|
|
731
|
+
for (const [fileIndex, file] of candidate.manifest.files.entries()) {
|
|
732
|
+
const parts = candidate.files[fileIndex];
|
|
733
|
+
if (!parts || parts.length !== file.chunks || parts.some((part) => part === null)) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
const bytes = new Uint8Array(file.size);
|
|
737
|
+
let offset = 0;
|
|
738
|
+
for (const part of parts) {
|
|
739
|
+
if (!part) {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
bytes.set(part, offset);
|
|
743
|
+
offset += part.length;
|
|
744
|
+
}
|
|
745
|
+
if (offset !== file.size) {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
const digest = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
749
|
+
if (digest !== file.sha256) {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
if (file.path === "client.js") {
|
|
753
|
+
source = new TextDecoder().decode(bytes);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return source;
|
|
757
|
+
}
|
|
758
|
+
function createPreviewManager(baseUrl, onEnded, onViews) {
|
|
759
|
+
const entries = /* @__PURE__ */ new Map();
|
|
760
|
+
const notify = (entry) => {
|
|
761
|
+
for (const listener of entry.listeners) {
|
|
762
|
+
listener();
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
const close = (entry) => {
|
|
766
|
+
if (entry.reconnect) {
|
|
767
|
+
clearTimeout(entry.reconnect);
|
|
768
|
+
}
|
|
769
|
+
entry.reconnect = null;
|
|
770
|
+
const socket = entry.socket;
|
|
771
|
+
entry.socket = null;
|
|
772
|
+
socket?.close();
|
|
773
|
+
entry.connecting = false;
|
|
774
|
+
entry.candidate = null;
|
|
775
|
+
};
|
|
776
|
+
const end = (entry) => {
|
|
777
|
+
close(entry);
|
|
778
|
+
entry.metadata = null;
|
|
779
|
+
entry.snapshot = empty;
|
|
780
|
+
notify(entry);
|
|
781
|
+
onEnded();
|
|
782
|
+
};
|
|
783
|
+
const handleEvent = async (entry, event) => {
|
|
784
|
+
if (event.type === "ended") {
|
|
785
|
+
end(entry);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (event.revision <= entry.snapshot.revision) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (event.type === "begin") {
|
|
792
|
+
entry.candidate = {
|
|
793
|
+
revision: event.revision,
|
|
794
|
+
manifest: event.manifest,
|
|
795
|
+
files: event.manifest.files.map((file) => Array.from({ length: file.chunks }, () => null))
|
|
796
|
+
};
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const candidate = entry.candidate;
|
|
800
|
+
if (!candidate || candidate.revision !== event.revision) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (event.type === "chunk") {
|
|
804
|
+
const file = candidate.manifest.files[event.fileIndex];
|
|
805
|
+
const slots = candidate.files[event.fileIndex];
|
|
806
|
+
if (!file || !slots || event.chunkIndex < 0 || event.chunkIndex >= slots.length || slots[event.chunkIndex]) {
|
|
807
|
+
entry.candidate = null;
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const bytes = bytesFromBase64(event.base64);
|
|
811
|
+
if (bytes.length !== Math.min(256 * 1024, file.size - event.chunkIndex * 256 * 1024)) {
|
|
812
|
+
entry.candidate = null;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
slots[event.chunkIndex] = bytes;
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (event.type === "complete") {
|
|
819
|
+
const source = await verify(candidate);
|
|
820
|
+
if (source !== null && entry.candidate === candidate && candidate.revision > entry.snapshot.revision) {
|
|
821
|
+
entry.snapshot = { revision: candidate.revision, source };
|
|
822
|
+
if (candidate.manifest.views !== void 0)
|
|
823
|
+
onViews?.(entry.app.id, candidate.manifest.views);
|
|
824
|
+
notify(entry);
|
|
825
|
+
}
|
|
826
|
+
if (entry.candidate === candidate) {
|
|
827
|
+
entry.candidate = null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
const connect = async (entry) => {
|
|
832
|
+
if (!entry.listeners.size || !entry.app.preview || entry.socket || entry.connecting) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
entry.connecting = true;
|
|
836
|
+
const appMetadata = entry.app.preview;
|
|
837
|
+
let metadata = entry.metadata && viewerTokenExpiresAt(entry.metadata) > Math.max(Date.now(), viewerTokenExpiresAt(appMetadata)) ? entry.metadata : appMetadata;
|
|
838
|
+
try {
|
|
839
|
+
const refresh = new URL("preview/metadata", baseUrl);
|
|
840
|
+
refresh.searchParams.set("sessionId", metadata.sessionId);
|
|
841
|
+
const response = await fetch(refresh, { credentials: "same-origin" });
|
|
842
|
+
if (response.ok) {
|
|
843
|
+
metadata = previewMetadataSchema.parse(await response.json());
|
|
844
|
+
entry.metadata = metadata;
|
|
845
|
+
} else if (response.status === 404) {
|
|
846
|
+
end(entry);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
} catch {
|
|
850
|
+
}
|
|
851
|
+
if (!entry.listeners.size) {
|
|
852
|
+
entry.connecting = false;
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const socket = new WebSocket(metadata.websocketUrl, metadata.token);
|
|
856
|
+
entry.socket = socket;
|
|
857
|
+
entry.connecting = false;
|
|
858
|
+
socket.addEventListener("open", () => {
|
|
859
|
+
entry.delay = 1e3;
|
|
860
|
+
const client = createPreviewWebSocketClient(socket);
|
|
861
|
+
void (async () => {
|
|
862
|
+
try {
|
|
863
|
+
const stream = await client.subscribe();
|
|
864
|
+
for await (const event of stream) {
|
|
865
|
+
await handleEvent(entry, event);
|
|
866
|
+
}
|
|
867
|
+
} catch {
|
|
868
|
+
} finally {
|
|
869
|
+
socket.close();
|
|
870
|
+
}
|
|
871
|
+
})();
|
|
872
|
+
});
|
|
873
|
+
socket.addEventListener("error", () => socket.close());
|
|
874
|
+
socket.addEventListener("close", () => {
|
|
875
|
+
if (entry.socket !== socket) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
entry.socket = null;
|
|
879
|
+
entry.candidate = null;
|
|
880
|
+
if (entry.listeners.size) {
|
|
881
|
+
entry.reconnect = setTimeout(() => {
|
|
882
|
+
entry.reconnect = null;
|
|
883
|
+
void connect(entry);
|
|
884
|
+
}, entry.delay);
|
|
885
|
+
entry.delay = Math.min(entry.delay * 2, 3e4);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
getSnapshot: (sessionId) => entries.get(sessionId)?.snapshot ?? empty,
|
|
891
|
+
updateApp: (app) => {
|
|
892
|
+
const sessionId = app.preview?.sessionId;
|
|
893
|
+
const entry = sessionId && entries.get(sessionId);
|
|
894
|
+
if (entry) {
|
|
895
|
+
entry.app = app;
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
subscribe: (app, listener) => {
|
|
899
|
+
const sessionId = app.preview?.sessionId;
|
|
900
|
+
if (!sessionId) {
|
|
901
|
+
return () => {
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
let entry = entries.get(sessionId);
|
|
905
|
+
if (!entry) {
|
|
906
|
+
entry = {
|
|
907
|
+
app,
|
|
908
|
+
metadata: null,
|
|
909
|
+
candidate: null,
|
|
910
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
911
|
+
snapshot: empty,
|
|
912
|
+
socket: null,
|
|
913
|
+
connecting: false,
|
|
914
|
+
reconnect: null,
|
|
915
|
+
delay: 1e3
|
|
916
|
+
};
|
|
917
|
+
entries.set(sessionId, entry);
|
|
918
|
+
}
|
|
919
|
+
entry.app = app;
|
|
920
|
+
entry.listeners.add(listener);
|
|
921
|
+
void connect(entry);
|
|
922
|
+
return () => {
|
|
923
|
+
entry?.listeners.delete(listener);
|
|
924
|
+
if (entry && !entry.listeners.size) {
|
|
925
|
+
close(entry);
|
|
926
|
+
entries.delete(sessionId);
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
},
|
|
930
|
+
dispose: () => {
|
|
931
|
+
for (const entry of entries.values()) {
|
|
932
|
+
close(entry);
|
|
933
|
+
}
|
|
934
|
+
entries.clear();
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/store.ts
|
|
940
|
+
function createTailorKitStore(baseUrlInput, initialApps) {
|
|
941
|
+
const baseUrl = toBaseUrl(baseUrlInput);
|
|
942
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
943
|
+
let providedApps = initialApps;
|
|
944
|
+
let appsSnapshot = {
|
|
945
|
+
apps: initialApps ?? [],
|
|
946
|
+
views: listViews(initialApps ?? []),
|
|
947
|
+
error: null,
|
|
948
|
+
status: initialApps === void 0 ? "idle" : "ready"
|
|
949
|
+
};
|
|
950
|
+
let appsPromise = null;
|
|
951
|
+
let appsRequestId = 0;
|
|
952
|
+
let appsRequested = false;
|
|
953
|
+
let appsSubscribers = 0;
|
|
954
|
+
let metaSnapshot = {
|
|
955
|
+
assetsBaseUrl: null,
|
|
956
|
+
error: null,
|
|
957
|
+
schema: null,
|
|
958
|
+
status: "idle"
|
|
959
|
+
};
|
|
960
|
+
let fetchMetaPromise = null;
|
|
961
|
+
let metaRequestId = 0;
|
|
962
|
+
const emit = () => {
|
|
963
|
+
for (const listener of listeners) {
|
|
964
|
+
listener();
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
const store = {
|
|
968
|
+
baseUrl,
|
|
969
|
+
views: createViewRegistry(),
|
|
970
|
+
setProvidedApps(apps) {
|
|
971
|
+
if (providedApps === apps) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
providedApps = apps;
|
|
975
|
+
appsRequestId += 1;
|
|
976
|
+
appsPromise = null;
|
|
977
|
+
appsSnapshot = {
|
|
978
|
+
apps: apps ?? [],
|
|
979
|
+
views: listViews(apps ?? []),
|
|
980
|
+
error: null,
|
|
981
|
+
status: apps === void 0 ? "idle" : "ready"
|
|
982
|
+
};
|
|
983
|
+
emit();
|
|
984
|
+
if (apps === void 0 && appsRequested && appsSubscribers > 0) {
|
|
985
|
+
void store.fetchApps();
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
fetchApps: (options = {}) => {
|
|
989
|
+
appsRequested = true;
|
|
990
|
+
if (providedApps !== void 0) {
|
|
991
|
+
return Promise.resolve();
|
|
992
|
+
}
|
|
993
|
+
if (appsPromise && !options.force) {
|
|
994
|
+
return appsPromise;
|
|
995
|
+
}
|
|
996
|
+
appsSnapshot = { ...appsSnapshot, error: null, status: "loading" };
|
|
997
|
+
emit();
|
|
998
|
+
const requestId = ++appsRequestId;
|
|
999
|
+
appsPromise = fetch(new URL("apps", baseUrl)).then(async (response) => {
|
|
1000
|
+
if (!response.ok) {
|
|
1001
|
+
throw new Error(`Unable to fetch TailorKit apps from ${baseUrl.toString()}.`);
|
|
1002
|
+
}
|
|
1003
|
+
const apps = await response.json();
|
|
1004
|
+
if (requestId !== appsRequestId) return;
|
|
1005
|
+
appsSnapshot = { apps, views: listViews(apps), error: null, status: "ready" };
|
|
1006
|
+
emit();
|
|
1007
|
+
}).catch((error) => {
|
|
1008
|
+
if (requestId !== appsRequestId) return;
|
|
1009
|
+
appsSnapshot = {
|
|
1010
|
+
...appsSnapshot,
|
|
1011
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1012
|
+
status: "error"
|
|
1013
|
+
};
|
|
1014
|
+
emit();
|
|
1015
|
+
});
|
|
1016
|
+
return appsPromise;
|
|
1017
|
+
},
|
|
1018
|
+
fetchMeta: (options = {}) => {
|
|
1019
|
+
if (fetchMetaPromise && !options.force) {
|
|
1020
|
+
return fetchMetaPromise;
|
|
1021
|
+
}
|
|
1022
|
+
metaSnapshot = { ...metaSnapshot, error: null, status: "loading" };
|
|
1023
|
+
emit();
|
|
1024
|
+
const requestId = ++metaRequestId;
|
|
1025
|
+
fetchMetaPromise = fetch(new URL("meta", baseUrl)).then(async (response) => {
|
|
1026
|
+
if (!response.ok) {
|
|
1027
|
+
throw new Error(`Unable to fetch TailorKit metadata from ${baseUrl.toString()}.`);
|
|
1028
|
+
}
|
|
1029
|
+
const meta = await response.json();
|
|
1030
|
+
if (requestId !== metaRequestId) return;
|
|
1031
|
+
metaSnapshot = {
|
|
1032
|
+
assetsBaseUrl: meta.assetsBaseUrl ?? null,
|
|
1033
|
+
error: null,
|
|
1034
|
+
schema: meta.schema,
|
|
1035
|
+
status: "ready"
|
|
1036
|
+
};
|
|
1037
|
+
emit();
|
|
1038
|
+
}).catch((error) => {
|
|
1039
|
+
if (requestId !== metaRequestId) return;
|
|
1040
|
+
metaSnapshot = {
|
|
1041
|
+
...metaSnapshot,
|
|
1042
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1043
|
+
status: "error"
|
|
1044
|
+
};
|
|
1045
|
+
emit();
|
|
1046
|
+
});
|
|
1047
|
+
return fetchMetaPromise;
|
|
1048
|
+
},
|
|
1049
|
+
getAppsSnapshot: () => appsSnapshot,
|
|
1050
|
+
getMetaSnapshot: () => metaSnapshot,
|
|
1051
|
+
subscribe: (listener) => {
|
|
1052
|
+
listeners.add(listener);
|
|
1053
|
+
return () => {
|
|
1054
|
+
listeners.delete(listener);
|
|
1055
|
+
};
|
|
1056
|
+
},
|
|
1057
|
+
subscribeApps: (listener) => {
|
|
1058
|
+
appsSubscribers += 1;
|
|
1059
|
+
listeners.add(listener);
|
|
1060
|
+
return () => {
|
|
1061
|
+
listeners.delete(listener);
|
|
1062
|
+
appsSubscribers -= 1;
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
return {
|
|
1067
|
+
...store,
|
|
1068
|
+
previews: createPreviewManager(
|
|
1069
|
+
baseUrl,
|
|
1070
|
+
() => {
|
|
1071
|
+
if (appsRequested) void store.fetchApps({ force: true });
|
|
918
1072
|
},
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const unsubscribe = host.subscribe(() => {
|
|
924
|
-
const tree = host.getSnapshot();
|
|
925
|
-
if (tree !== null) {
|
|
926
|
-
store.setSnapshot(tree);
|
|
927
|
-
setStatus("ready");
|
|
1073
|
+
(appId, views) => {
|
|
1074
|
+
const apps = appsSnapshot.apps.map((app) => app.id === appId ? { ...app, views } : app);
|
|
1075
|
+
appsSnapshot = { ...appsSnapshot, apps, views: listViews(apps) };
|
|
1076
|
+
emit();
|
|
928
1077
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
setError(null);
|
|
932
|
-
host.mount();
|
|
933
|
-
return () => {
|
|
934
|
-
unsubscribe();
|
|
935
|
-
dispatchRef.current = null;
|
|
936
|
-
hostRef.current = null;
|
|
937
|
-
host.destroy();
|
|
938
|
-
};
|
|
939
|
-
}, [appUrl, createIframe, sourceText, getBackendSession, store]);
|
|
940
|
-
if (status === "error" && error) {
|
|
941
|
-
return createElement("div", null, formatError(error));
|
|
942
|
-
}
|
|
943
|
-
return ui;
|
|
1078
|
+
)
|
|
1079
|
+
};
|
|
944
1080
|
}
|
|
945
|
-
function
|
|
946
|
-
const
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (rootId === null) {
|
|
950
|
-
return null;
|
|
1081
|
+
function toBaseUrl(value) {
|
|
1082
|
+
const url = value instanceof URL ? new URL(value) : new URL(value);
|
|
1083
|
+
if (!url.pathname.endsWith("/")) {
|
|
1084
|
+
url.pathname = `${url.pathname}/`;
|
|
951
1085
|
}
|
|
952
|
-
return
|
|
1086
|
+
return url;
|
|
953
1087
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
props[binding.callback] = (...args) => {
|
|
964
|
-
dispatch({
|
|
965
|
-
data: {
|
|
966
|
-
args: args.slice(0, binding.inputCount),
|
|
967
|
-
event: binding.event,
|
|
968
|
-
nodeId: node.id
|
|
969
|
-
},
|
|
970
|
-
type: "dispatchCallback"
|
|
971
|
-
});
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
return createElement(component, props, ...children);
|
|
975
|
-
});
|
|
976
|
-
function RemoteViewComponent({ nodeId }) {
|
|
977
|
-
const ctx = useContext3(RemoteUIContext);
|
|
978
|
-
if (ctx === null) {
|
|
979
|
-
return null;
|
|
980
|
-
}
|
|
981
|
-
const { store } = ctx;
|
|
982
|
-
const subscribe = useCallback(
|
|
983
|
-
(listener) => store.subscribe(nodeId, listener),
|
|
984
|
-
[store, nodeId]
|
|
1088
|
+
function listViews(apps) {
|
|
1089
|
+
return apps.flatMap(
|
|
1090
|
+
(app) => (app.views ?? []).filter((view) => !view.disabled).map(({ slot, path, instances }) => ({
|
|
1091
|
+
id: JSON.stringify([app.id, slot, path]),
|
|
1092
|
+
...instances ? { instances } : {},
|
|
1093
|
+
app,
|
|
1094
|
+
slot,
|
|
1095
|
+
path
|
|
1096
|
+
}))
|
|
985
1097
|
);
|
|
986
|
-
const getSnapshot = useCallback(() => store.getNode(nodeId), [store, nodeId]);
|
|
987
|
-
const node = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
988
|
-
if (node === null) {
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
if (node.kind === "text") {
|
|
992
|
-
return node.text;
|
|
993
|
-
}
|
|
994
|
-
const childElements = node.children.map((child) => /* @__PURE__ */ jsx2(RemoteView, { nodeId: child.id }, child.id));
|
|
995
|
-
if (node.kind === "fragment") {
|
|
996
|
-
return createElement(Fragment, { key: node.id }, ...childElements);
|
|
997
|
-
}
|
|
998
|
-
return /* @__PURE__ */ jsx2(RemoteElementView, { node, ctx });
|
|
999
1098
|
}
|
|
1000
|
-
var RemoteView = memo(RemoteViewComponent);
|
|
1001
1099
|
|
|
1002
|
-
// src/components/
|
|
1100
|
+
// src/components/slot.tsx
|
|
1003
1101
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
createIframe,
|
|
1008
|
-
fallback = null,
|
|
1009
|
-
...viewProps
|
|
1010
|
-
}) => {
|
|
1011
|
-
const { store, client } = useTailorRootContext("AppView");
|
|
1012
|
-
const { theme, components: wrappedComponents } = client;
|
|
1013
|
-
const reactId = useId2();
|
|
1014
|
-
const getBackendSession = useMemo3(
|
|
1015
|
-
() => createSessionProvider({ baseUrl: store.baseUrl, appId: app.id }),
|
|
1016
|
-
[store.baseUrl, app.id]
|
|
1017
|
-
);
|
|
1018
|
-
const currentView = useSyncExternalStore2(
|
|
1102
|
+
function ManagedSlot({ app, name, instanceKey }) {
|
|
1103
|
+
const { store } = useTailorRootContext("Slot");
|
|
1104
|
+
const state = useSyncExternalStore3(
|
|
1019
1105
|
store.views.subscribe,
|
|
1020
1106
|
store.views.getSnapshot,
|
|
1021
1107
|
store.views.getSnapshot
|
|
1022
1108
|
);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1109
|
+
if (state === null) return null;
|
|
1110
|
+
return instanceKey === void 0 ? /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state }) : /* @__PURE__ */ jsx3(InstanceSlot, { app, name, instanceKey, state });
|
|
1111
|
+
}
|
|
1112
|
+
function InstanceSlot({
|
|
1113
|
+
app,
|
|
1114
|
+
name,
|
|
1115
|
+
instanceKey,
|
|
1116
|
+
state
|
|
1117
|
+
}) {
|
|
1118
|
+
const { store } = useTailorRootContext("Slot");
|
|
1119
|
+
const instances = useSlotInstances({ app, slot: name });
|
|
1120
|
+
const meta = useSyncExternalStore3(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
|
|
1121
|
+
const instance = instances.data?.find((instance2) => instance2.key === instanceKey);
|
|
1122
|
+
const resolved = instances.isSuccess && instance && meta.schema ? resolveSlotView(app.views ?? [], name, state, meta.schema) : null;
|
|
1123
|
+
const view = resolved?.status === "ready" ? resolved.view : void 0;
|
|
1124
|
+
const stableContext = useStableContext(resolved?.context);
|
|
1125
|
+
const stableInstance = useStableContext(instance);
|
|
1126
|
+
const readyState = useMemo4(
|
|
1127
|
+
() => view === void 0 || !stableInstance ? null : {
|
|
1128
|
+
controlled: true,
|
|
1129
|
+
view,
|
|
1130
|
+
context: stableContext,
|
|
1131
|
+
status: "ready",
|
|
1132
|
+
instance: stableInstance
|
|
1133
|
+
},
|
|
1134
|
+
[view, stableContext, stableInstance]
|
|
1135
|
+
);
|
|
1136
|
+
if (instances.isPending) return /* @__PURE__ */ jsx3("div", { role: "status", children: "Loading view\u2026" });
|
|
1137
|
+
if (instances.error) return /* @__PURE__ */ jsx3("div", { role: "alert", children: instances.error.message });
|
|
1138
|
+
if (!instance) return /* @__PURE__ */ jsxs2("div", { role: "alert", children: [
|
|
1139
|
+
'View instance "',
|
|
1140
|
+
instanceKey,
|
|
1141
|
+
'" is unavailable.'
|
|
1142
|
+
] });
|
|
1143
|
+
if (!readyState) return null;
|
|
1144
|
+
return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state: readyState });
|
|
1145
|
+
}
|
|
1146
|
+
function ControlledSlot({
|
|
1147
|
+
app,
|
|
1148
|
+
name,
|
|
1149
|
+
view,
|
|
1150
|
+
context,
|
|
1151
|
+
status,
|
|
1152
|
+
instance
|
|
1153
|
+
}) {
|
|
1154
|
+
useTailorRootContext("Slot.Controlled");
|
|
1155
|
+
const stableContext = useStableContext(context);
|
|
1156
|
+
const stableInstance = useStableContext(instance);
|
|
1157
|
+
const state = useMemo4(
|
|
1158
|
+
() => ({
|
|
1159
|
+
controlled: true,
|
|
1160
|
+
view,
|
|
1161
|
+
context: stableContext,
|
|
1162
|
+
status,
|
|
1163
|
+
...status === "ready" && stableInstance ? { instance: stableInstance } : {}
|
|
1164
|
+
}),
|
|
1165
|
+
[view, stableContext, status, stableInstance]
|
|
1166
|
+
);
|
|
1167
|
+
if (status === "ready" && !instance && app.views?.some(
|
|
1168
|
+
(entry) => entry.slot === name && entry.path === view && entry.instances && !entry.disabled
|
|
1169
|
+
)) {
|
|
1170
|
+
return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance is required. Pass instance to Slot.Controlled." });
|
|
1171
|
+
}
|
|
1172
|
+
return /* @__PURE__ */ jsx3(SlotRenderer, { app, name, state });
|
|
1173
|
+
}
|
|
1174
|
+
var Slot = Object.assign(ManagedSlot, { Controlled: ControlledSlot });
|
|
1175
|
+
function SlotRenderer({ app, name, state }) {
|
|
1176
|
+
const { store, client } = useTailorRootContext("Slot");
|
|
1177
|
+
const reactId = useId2();
|
|
1178
|
+
const getBackendSession = useMemo4(
|
|
1179
|
+
() => createSessionProvider2({ baseUrl: store.baseUrl, appId: app.id }),
|
|
1180
|
+
[store.baseUrl, app.id, app.currentDeployment?.id]
|
|
1181
|
+
);
|
|
1182
|
+
const meta = useSyncExternalStore3(store.subscribe, store.getMetaSnapshot, store.getMetaSnapshot);
|
|
1043
1183
|
const previewSessionId = app.preview?.sessionId ?? "";
|
|
1044
|
-
const appRef =
|
|
1184
|
+
const appRef = useRef4(app);
|
|
1045
1185
|
appRef.current = app;
|
|
1046
|
-
const subscribePreview =
|
|
1186
|
+
const subscribePreview = useCallback3(
|
|
1047
1187
|
(listener) => {
|
|
1048
1188
|
const currentApp = appRef.current;
|
|
1049
1189
|
return currentApp.preview?.sessionId === previewSessionId ? store.previews.subscribe(currentApp, listener) : () => {
|
|
@@ -1051,85 +1191,75 @@ var AppView = ({
|
|
|
1051
1191
|
},
|
|
1052
1192
|
[store, previewSessionId]
|
|
1053
1193
|
);
|
|
1054
|
-
const getPreviewSnapshot =
|
|
1194
|
+
const getPreviewSnapshot = useCallback3(
|
|
1055
1195
|
() => store.previews.getSnapshot(previewSessionId),
|
|
1056
1196
|
[store, previewSessionId]
|
|
1057
1197
|
);
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
useEffect2(() => {
|
|
1064
|
-
store.previews.updateApp(app);
|
|
1065
|
-
}, [store, app]);
|
|
1066
|
-
const runtimeProps = useMemo3(
|
|
1067
|
-
() => props === void 0 ? void 0 : {
|
|
1068
|
-
...props,
|
|
1198
|
+
const preview = useSyncExternalStore3(subscribePreview, getPreviewSnapshot, getPreviewSnapshot);
|
|
1199
|
+
const props = useMemo4(
|
|
1200
|
+
() => ({
|
|
1201
|
+
...state,
|
|
1202
|
+
slot: name,
|
|
1069
1203
|
declaredViews: Object.keys(meta.schema?.views ?? {}),
|
|
1070
|
-
supportedViews: meta.schema?.slots[
|
|
1071
|
-
},
|
|
1072
|
-
[
|
|
1073
|
-
);
|
|
1074
|
-
const assetsBaseUrl = app.clientPath ? null : meta.assetsBaseUrl;
|
|
1075
|
-
const appUrl = useMemo3(
|
|
1076
|
-
() => resolveAppUrl(app, store.baseUrl, assetsBaseUrl),
|
|
1077
|
-
[app, assetsBaseUrl, store.baseUrl]
|
|
1204
|
+
supportedViews: meta.schema?.slots[name]?.views ?? []
|
|
1205
|
+
}),
|
|
1206
|
+
[state, name, meta.schema]
|
|
1078
1207
|
);
|
|
1079
|
-
|
|
1208
|
+
const appUrl = useMemo4(() => {
|
|
1209
|
+
if (app.clientPath) return new URL(app.clientPath, store.baseUrl);
|
|
1210
|
+
if (!meta.assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) return null;
|
|
1211
|
+
return new URL(
|
|
1212
|
+
`projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/client/client.js`,
|
|
1213
|
+
toBaseUrl(meta.assetsBaseUrl)
|
|
1214
|
+
);
|
|
1215
|
+
}, [app, store.baseUrl, meta.assetsBaseUrl]);
|
|
1216
|
+
useEffect3(() => {
|
|
1080
1217
|
void store.fetchMeta();
|
|
1081
1218
|
}, [store]);
|
|
1082
|
-
|
|
1083
|
-
|
|
1219
|
+
useEffect3(() => {
|
|
1220
|
+
store.previews.updateApp(app);
|
|
1221
|
+
}, [store, app]);
|
|
1222
|
+
if (meta.schema === null || appUrl === null && preview.source === null) return null;
|
|
1223
|
+
if (!("controlled" in state)) {
|
|
1224
|
+
const selected = selectSlotView(app.views ?? [], name, state.view, meta.schema);
|
|
1225
|
+
if (selected?.instances)
|
|
1226
|
+
return /* @__PURE__ */ jsx3("div", { role: "alert", children: "A view instance key is required. Pass instanceKey to Slot." });
|
|
1084
1227
|
}
|
|
1085
1228
|
const viewId = `tailorkit-view-${reactId.replaceAll(":", "")}`;
|
|
1086
|
-
return /* @__PURE__ */ jsx3(PrimitiveThemeContext.Provider, { value: { viewId, theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-view": viewId, children: [
|
|
1087
|
-
/* @__PURE__ */ jsx3("style", { "data-tailorkit-theme-style": viewId, children: buildThemeCss(viewId, theme) }),
|
|
1229
|
+
return /* @__PURE__ */ jsx3(PrimitiveThemeContext.Provider, { value: { viewId, theme: client.theme }, children: /* @__PURE__ */ jsxs2("div", { "data-tailorkit-view": viewId, children: [
|
|
1230
|
+
/* @__PURE__ */ jsx3("style", { "data-tailorkit-theme-style": viewId, children: buildThemeCss(viewId, client.theme) }),
|
|
1088
1231
|
/* @__PURE__ */ jsx3(
|
|
1089
1232
|
RemoteViewHost,
|
|
1090
1233
|
{
|
|
1091
1234
|
appUrl: (appUrl ?? new URL(`preview/${previewSessionId}/client.js`, store.baseUrl)).toString(),
|
|
1092
|
-
sourceText:
|
|
1235
|
+
sourceText: preview.source ?? void 0,
|
|
1093
1236
|
getBackendSession,
|
|
1094
|
-
components:
|
|
1095
|
-
|
|
1096
|
-
props: runtimeProps
|
|
1237
|
+
components: client.components,
|
|
1238
|
+
props
|
|
1097
1239
|
},
|
|
1098
|
-
|
|
1240
|
+
preview.revision || appUrl?.toString()
|
|
1099
1241
|
)
|
|
1100
1242
|
] }) });
|
|
1101
|
-
};
|
|
1102
|
-
function resolveAppUrl(app, baseUrl, assetsBaseUrl) {
|
|
1103
|
-
if (app.clientPath) {
|
|
1104
|
-
return new URL(app.clientPath, baseUrl);
|
|
1105
|
-
}
|
|
1106
|
-
if (!assetsBaseUrl || !app.projectId || !app.currentDeployment?.id) {
|
|
1107
|
-
return null;
|
|
1108
|
-
}
|
|
1109
|
-
return new URL(
|
|
1110
|
-
`projects/${app.projectId}/apps/${app.id}/deployments/${app.currentDeployment.id}/client/client.js`,
|
|
1111
|
-
toBaseUrl(assetsBaseUrl)
|
|
1112
|
-
);
|
|
1113
1243
|
}
|
|
1114
1244
|
|
|
1115
1245
|
// src/tailorkit.tsx
|
|
1116
1246
|
import { createElement as createElement2 } from "react";
|
|
1117
1247
|
|
|
1118
|
-
// src/hooks/use-view.ts
|
|
1119
|
-
import { useEffect as
|
|
1120
|
-
function
|
|
1121
|
-
const { store } = useTailorRootContext("
|
|
1122
|
-
const id =
|
|
1248
|
+
// src/hooks/use-register-view.ts
|
|
1249
|
+
import { useEffect as useEffect4, useMemo as useMemo5 } from "react";
|
|
1250
|
+
function useRegisterView(view, options) {
|
|
1251
|
+
const { store } = useTailorRootContext("useRegisterView");
|
|
1252
|
+
const id = useMemo5(() => /* @__PURE__ */ Symbol("tailorkit-current-view"), []);
|
|
1123
1253
|
const status = options.status ?? "ready";
|
|
1124
1254
|
const context = "context" in options ? options.context : void 0;
|
|
1125
1255
|
const contextSnapshot = useStableContext(context);
|
|
1126
|
-
|
|
1256
|
+
useEffect4(
|
|
1127
1257
|
() => () => {
|
|
1128
1258
|
store.views.unregister(id);
|
|
1129
1259
|
},
|
|
1130
1260
|
[id, store]
|
|
1131
1261
|
);
|
|
1132
|
-
|
|
1262
|
+
useEffect4(() => {
|
|
1133
1263
|
store.views.register({
|
|
1134
1264
|
context: contextSnapshot,
|
|
1135
1265
|
id,
|
|
@@ -1140,29 +1270,45 @@ function useView(view, options) {
|
|
|
1140
1270
|
}
|
|
1141
1271
|
|
|
1142
1272
|
// src/hooks/use-apps.ts
|
|
1143
|
-
import { useCallback as
|
|
1273
|
+
import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
1274
|
+
|
|
1275
|
+
// src/scope-query.ts
|
|
1276
|
+
function normalizeScopeSelection(scopes) {
|
|
1277
|
+
if (scopes === void 0) {
|
|
1278
|
+
return { key: "*" };
|
|
1279
|
+
}
|
|
1280
|
+
const names = [...new Set(scopes)].sort();
|
|
1281
|
+
return { key: JSON.stringify(names), scopes: names };
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/hooks/use-apps.ts
|
|
1144
1285
|
function useApps(options = {}) {
|
|
1145
1286
|
return useAppsStore(useTailorRootContext("useApps").store, options);
|
|
1146
1287
|
}
|
|
1147
1288
|
function useAppsStore(store, options = {}) {
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
subscribe,
|
|
1155
|
-
() => store.getAppsSnapshot(selection.scopes),
|
|
1156
|
-
() => store.getAppsSnapshot(selection.scopes)
|
|
1289
|
+
const snapshot = useAppsSnapshot(store);
|
|
1290
|
+
const scopes = normalizeScopeSelection(options.scopes);
|
|
1291
|
+
const appIds = normalizeScopeSelection(options.appIds);
|
|
1292
|
+
const data = useMemo6(
|
|
1293
|
+
() => snapshot.status === "ready" ? snapshot.apps.filter((app) => matchesApp(app, scopes.scopes, appIds.scopes)) : void 0,
|
|
1294
|
+
[snapshot, scopes.key, appIds.key]
|
|
1157
1295
|
);
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1296
|
+
const refetch = useCallback4(() => store.fetchApps({ force: true }), [store]);
|
|
1297
|
+
return { ...toUseAppsResult(snapshot, refetch), data };
|
|
1298
|
+
}
|
|
1299
|
+
function matchesApp(app, scopes, appIds) {
|
|
1300
|
+
return (scopes === void 0 || app.scope !== void 0 && scopes.includes(app.scope.name)) && (appIds === void 0 || appIds.includes(app.id));
|
|
1301
|
+
}
|
|
1302
|
+
function useAppsSnapshot(store) {
|
|
1303
|
+
const snapshot = useSyncExternalStore4(
|
|
1304
|
+
store.subscribeApps,
|
|
1305
|
+
store.getAppsSnapshot,
|
|
1306
|
+
store.getAppsSnapshot
|
|
1164
1307
|
);
|
|
1165
|
-
|
|
1308
|
+
useEffect5(() => {
|
|
1309
|
+
void store.fetchApps();
|
|
1310
|
+
}, [store]);
|
|
1311
|
+
return snapshot;
|
|
1166
1312
|
}
|
|
1167
1313
|
function toUseAppsResult(snapshot, refetch) {
|
|
1168
1314
|
return {
|
|
@@ -1177,6 +1323,23 @@ function toUseAppsResult(snapshot, refetch) {
|
|
|
1177
1323
|
};
|
|
1178
1324
|
}
|
|
1179
1325
|
|
|
1326
|
+
// src/hooks/use-views.ts
|
|
1327
|
+
import { useCallback as useCallback5, useMemo as useMemo7 } from "react";
|
|
1328
|
+
function useViews(options = {}) {
|
|
1329
|
+
const { store } = useTailorRootContext("useViews");
|
|
1330
|
+
const snapshot = useAppsSnapshot(store);
|
|
1331
|
+
const scopes = normalizeScopeSelection(options.scopes);
|
|
1332
|
+
const appIds = normalizeScopeSelection(options.appIds);
|
|
1333
|
+
const data = useMemo7(
|
|
1334
|
+
() => snapshot.status === "ready" ? snapshot.views.filter(
|
|
1335
|
+
(view) => (options.slot === void 0 || view.slot === options.slot) && matchesApp(view.app, scopes.scopes, appIds.scopes)
|
|
1336
|
+
) : void 0,
|
|
1337
|
+
[snapshot, scopes.key, appIds.key, options.slot]
|
|
1338
|
+
);
|
|
1339
|
+
const refetch = useCallback5(() => store.fetchApps({ force: true }), [store]);
|
|
1340
|
+
return { ...toUseAppsResult(snapshot, refetch), data };
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1180
1343
|
// src/tailorkit.tsx
|
|
1181
1344
|
var componentTagPrefix = "tailorkit-";
|
|
1182
1345
|
var toComponentTagName = (name) => `${componentTagPrefix}${name.replaceAll(/([a-z0-9])([A-Z])/gu, "$1-$2").replaceAll(/[\s_]+/gu, "-").toLowerCase()}`;
|
|
@@ -1211,37 +1374,51 @@ function createReactTailorKitClient(options) {
|
|
|
1211
1374
|
};
|
|
1212
1375
|
const client = {
|
|
1213
1376
|
...clientConfig,
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1377
|
+
Slot: Object.assign(
|
|
1378
|
+
function ClientSlot(props) {
|
|
1379
|
+
useTailorRootContext("Slot", client);
|
|
1380
|
+
const TypedReactSlot = Slot;
|
|
1381
|
+
return createElement2(TypedReactSlot, props);
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
Controlled: function ClientControlledSlot(props) {
|
|
1385
|
+
useTailorRootContext("Slot.Controlled", client);
|
|
1386
|
+
const TypedControlledSlot = Slot.Controlled;
|
|
1387
|
+
return createElement2(TypedControlledSlot, props);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
),
|
|
1222
1391
|
useApps: function useClientApps(options2) {
|
|
1223
1392
|
useTailorRootContext("useApps", client);
|
|
1224
1393
|
return useApps(options2);
|
|
1225
1394
|
},
|
|
1226
|
-
|
|
1227
|
-
useTailorRootContext("
|
|
1228
|
-
|
|
1395
|
+
useViews: function useClientViews(options2) {
|
|
1396
|
+
useTailorRootContext("useViews", client);
|
|
1397
|
+
return useViews(options2);
|
|
1398
|
+
},
|
|
1399
|
+
useSlotInstances: function useClientSlotInstances(options2) {
|
|
1400
|
+
useTailorRootContext("useSlotInstances", client);
|
|
1401
|
+
return useSlotInstances(options2);
|
|
1402
|
+
},
|
|
1403
|
+
useRegisterView: function useClientRegisterView(view, options2) {
|
|
1404
|
+
useTailorRootContext("useRegisterView", client);
|
|
1405
|
+
useRegisterView(view, options2);
|
|
1229
1406
|
}
|
|
1230
1407
|
};
|
|
1231
1408
|
return client;
|
|
1232
1409
|
}
|
|
1233
1410
|
|
|
1234
1411
|
// src/components/root.tsx
|
|
1235
|
-
import { useEffect as
|
|
1412
|
+
import { useEffect as useEffect6, useMemo as useMemo9, useState as useState3 } from "react";
|
|
1236
1413
|
|
|
1237
1414
|
// src/components/render.tsx
|
|
1238
|
-
import { cloneElement, createElement as createElement3, isValidElement, useMemo as
|
|
1415
|
+
import { cloneElement, createElement as createElement3, isValidElement, useMemo as useMemo8 } from "react";
|
|
1239
1416
|
function useRender({
|
|
1240
1417
|
defaultTagName,
|
|
1241
1418
|
props,
|
|
1242
1419
|
render
|
|
1243
1420
|
}) {
|
|
1244
|
-
return
|
|
1421
|
+
return useMemo8(() => {
|
|
1245
1422
|
if (render) {
|
|
1246
1423
|
if (!isValidElement(render)) {
|
|
1247
1424
|
throw new Error("The render prop must be a valid React element.");
|
|
@@ -1311,17 +1488,17 @@ function isEventHandler(key) {
|
|
|
1311
1488
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1312
1489
|
function Root({ apps: appsProp, children, render, client, ...props }) {
|
|
1313
1490
|
const baseUrl = toBaseUrl(client.baseUrl).toString();
|
|
1314
|
-
const [previousStore, setStore] =
|
|
1491
|
+
const [previousStore, setStore] = useState3(() => createTailorKitStore(baseUrl, appsProp));
|
|
1315
1492
|
let store = previousStore;
|
|
1316
1493
|
if (previousStore.baseUrl.toString() !== baseUrl) {
|
|
1317
1494
|
store = createTailorKitStore(baseUrl, appsProp);
|
|
1318
1495
|
setStore(store);
|
|
1319
1496
|
}
|
|
1320
|
-
|
|
1497
|
+
useEffect6(() => {
|
|
1321
1498
|
store.setProvidedApps(appsProp);
|
|
1322
1499
|
}, [store, appsProp]);
|
|
1323
|
-
|
|
1324
|
-
const context =
|
|
1500
|
+
useEffect6(() => () => store.previews.dispose(), [store]);
|
|
1501
|
+
const context = useMemo9(
|
|
1325
1502
|
() => ({
|
|
1326
1503
|
store,
|
|
1327
1504
|
client
|
|
@@ -1336,8 +1513,8 @@ function Root({ apps: appsProp, children, render, client, ...props }) {
|
|
|
1336
1513
|
return /* @__PURE__ */ jsx4(TailorRootContext.Provider, { value: context, children: element });
|
|
1337
1514
|
}
|
|
1338
1515
|
export {
|
|
1339
|
-
AppView,
|
|
1340
1516
|
Root,
|
|
1517
|
+
Slot,
|
|
1341
1518
|
components,
|
|
1342
1519
|
createTailorKitClient,
|
|
1343
1520
|
primitives
|