@weaverse/next 0.1.0-alpha.0 → 0.1.0-alpha.1

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/index.js CHANGED
@@ -132,18 +132,53 @@ var NextClient = class {
132
132
  function createWeaverseNextClient(config) {
133
133
  return new NextClient(config);
134
134
  }
135
+
136
+ // src/theme-settings-store.ts
137
+ function createWeaverseNextThemeSettingsStore(options = {}) {
138
+ let listeners = /* @__PURE__ */ new Set();
139
+ let store = {
140
+ publicEnv: options.publicEnv,
141
+ schema: options.schema,
142
+ settings: { ...options.settings ?? {} },
143
+ getServerSnapshot: () => store.settings,
144
+ getSnapshot: () => store.settings,
145
+ subscribe: (listener) => {
146
+ listeners.add(listener);
147
+ return () => {
148
+ listeners.delete(listener);
149
+ };
150
+ },
151
+ updateThemeSettings: (next) => {
152
+ store.settings = { ...store.settings, ...next };
153
+ for (let listener of listeners) {
154
+ listener();
155
+ }
156
+ }
157
+ };
158
+ return store;
159
+ }
160
+ var EMPTY_THEME_SETTINGS = {};
135
161
  var WeaverseNextContext = react$1.createContext(null);
136
162
  function WeaverseNextProvider(props) {
137
163
  let { children, client, rootData, pageData, commerce, themeSettings } = props;
164
+ let themeSettingsValue = themeSettings ?? client?.themeSettings ?? EMPTY_THEME_SETTINGS;
165
+ let themeSettingsStore = react$1.useMemo(
166
+ () => createWeaverseNextThemeSettingsStore({
167
+ schema: client?.themeSchema,
168
+ settings: themeSettingsValue
169
+ }),
170
+ [client?.themeSchema, themeSettingsValue]
171
+ );
138
172
  let value = react$1.useMemo(
139
173
  () => ({
140
174
  client,
141
175
  rootData,
142
176
  pageData,
143
177
  commerce: commerce ?? client?.commerce,
144
- themeSettings: themeSettings ?? client?.themeSettings ?? {}
178
+ themeSettings: themeSettingsStore.getSnapshot(),
179
+ themeSettingsStore
145
180
  }),
146
- [client, rootData, pageData, commerce, themeSettings]
181
+ [client, rootData, pageData, commerce, themeSettingsStore]
147
182
  );
148
183
  return /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextContext.Provider, { value, children });
149
184
  }
@@ -167,7 +202,12 @@ function useWeaverseCommerce() {
167
202
  return useWeaverseNextContext("useWeaverseCommerce").commerce;
168
203
  }
169
204
  function useThemeSettings() {
170
- return useWeaverseNextContext("useThemeSettings").themeSettings;
205
+ let { themeSettingsStore } = useWeaverseNextContext("useThemeSettings");
206
+ return react$1.useSyncExternalStore(
207
+ themeSettingsStore.subscribe,
208
+ themeSettingsStore.getSnapshot,
209
+ themeSettingsStore.getServerSnapshot
210
+ );
171
211
  }
172
212
 
173
213
  // src/loader.ts
@@ -246,8 +286,262 @@ async function runWeaverseComponentLoaders(args) {
246
286
  await walk(items);
247
287
  return data;
248
288
  }
249
- var EMPTY_DATA_CONTEXT = {};
289
+
290
+ // src/request-info.ts
291
+ function toUrl(value) {
292
+ return typeof value === "string" ? new URL(value, "http://localhost") : value;
293
+ }
294
+ function getSearchParamsFromContext(context) {
295
+ if (context?.searchParams) {
296
+ return new URLSearchParams(context.searchParams);
297
+ }
298
+ if (context?.url) {
299
+ let url = toUrl(context.url);
300
+ return new URLSearchParams(url.searchParams);
301
+ }
302
+ return new URLSearchParams();
303
+ }
304
+ function getPathnameFromContext(context) {
305
+ if (context?.pathname) {
306
+ return context.pathname;
307
+ }
308
+ if (context?.url) {
309
+ let url = toUrl(context.url);
310
+ return url.pathname || "/";
311
+ }
312
+ return "/";
313
+ }
314
+ function coerceQueryValue(value) {
315
+ if (value === "true") {
316
+ return true;
317
+ }
318
+ if (value === "false") {
319
+ return false;
320
+ }
321
+ return value;
322
+ }
323
+ function toQueryRecord(searchParams) {
324
+ let queries = {};
325
+ for (let [key, value] of searchParams.entries()) {
326
+ queries[key] = coerceQueryValue(value);
327
+ }
328
+ return queries;
329
+ }
330
+ function buildWeaverseNextRequestInfo(context) {
331
+ let searchParams = getSearchParamsFromContext(context);
332
+ let search = searchParams.toString();
333
+ return {
334
+ pathname: getPathnameFromContext(context),
335
+ search: search ? `?${search}` : "",
336
+ queries: toQueryRecord(searchParams),
337
+ ...context?.i18n ? { i18n: context.i18n } : {}
338
+ };
339
+ }
340
+
341
+ // src/runtime.ts
342
+ function getRuntimeWindow() {
343
+ return typeof window === "undefined" ? void 0 : window;
344
+ }
250
345
  function getRenderablePage(data) {
346
+ let page = data.page;
347
+ let rootId = page.rootId ?? page.items.find((item) => item.type === "main")?.id ?? page.items[0]?.id ?? page.id;
348
+ return { ...page, rootId };
349
+ }
350
+ function getConfigString(configs, key) {
351
+ let value = configs?.[key];
352
+ return typeof value === "string" ? value : void 0;
353
+ }
354
+ function getRecordString(record, key) {
355
+ let value = record?.[key];
356
+ return typeof value === "string" ? value : void 0;
357
+ }
358
+ function resolveProjectId(client, data, fallback) {
359
+ let projectId = client?.projectId;
360
+ if (projectId) {
361
+ return projectId;
362
+ }
363
+ let configProjectId = getConfigString(data.configs, "projectId");
364
+ if (configProjectId) {
365
+ return configProjectId;
366
+ }
367
+ return getRecordString(data.project, "id") ?? fallback;
368
+ }
369
+ function resolveDataContext(config) {
370
+ if (config.dataContext) {
371
+ return config.dataContext;
372
+ }
373
+ if (config.client?.dataContext) {
374
+ return config.client.dataContext;
375
+ }
376
+ return {};
377
+ }
378
+ function getRuntimeKey(pageId, requestInfo) {
379
+ return `${pageId}:${requestInfo.pathname}:${requestInfo.search}`;
380
+ }
381
+ function registerRuntime(runtime) {
382
+ let runtimeWindow = getRuntimeWindow();
383
+ if (!runtimeWindow) {
384
+ return;
385
+ }
386
+ runtimeWindow.__weaverses ??= {};
387
+ runtimeWindow.__weaverses[runtime.pageId] = runtime;
388
+ runtimeWindow.__weaverse = runtime;
389
+ }
390
+ var WeaverseNextRuntime = class extends react.Weaverse {
391
+ pageId;
392
+ internal;
393
+ requestInfo;
394
+ isPreviewMode;
395
+ isRevisionPreview;
396
+ sectionType;
397
+ translationMap = {};
398
+ translationLocale = "";
399
+ translationLanguageId = "";
400
+ constructor(config) {
401
+ let { client, data } = config;
402
+ let page = getRenderablePage(data);
403
+ let configs = data.configs;
404
+ let requestContext = client?.requestContext;
405
+ let projectId = resolveProjectId(client, data, page.id);
406
+ ensureNextItemConstructor();
407
+ super({
408
+ projectId,
409
+ data: page,
410
+ isDesignMode: requestContext?.isDesignMode ?? false,
411
+ weaverseHost: getConfigString(configs, "weaverseHost"),
412
+ weaverseVersion: getConfigString(configs, "weaverseVersion")
413
+ });
414
+ this.pageId = page.id;
415
+ this.dataContext = resolveDataContext(config);
416
+ this.requestInfo = buildWeaverseNextRequestInfo(requestContext);
417
+ this.isDesignMode = requestContext?.isDesignMode ?? false;
418
+ this.isPreviewMode = requestContext?.isPreviewMode ?? false;
419
+ this.isRevisionPreview = requestContext?.isRevisionPreview ?? false;
420
+ this.sectionType = requestContext?.sectionType;
421
+ this.internal = {
422
+ navigate: config.navigate,
423
+ pageAssignment: data.pageAssignment,
424
+ project: data.project,
425
+ revalidate: config.revalidate,
426
+ themeSettingsStore: config.themeSettingsStore ?? createWeaverseNextThemeSettingsStore({
427
+ schema: client?.themeSchema,
428
+ settings: client?.themeSettings
429
+ })
430
+ };
431
+ }
432
+ extractTranslationSidecar = () => void 0;
433
+ setTranslationSidecar = (map = {}, locale = "", languageId = "") => {
434
+ this.translationMap = map;
435
+ this.translationLocale = locale;
436
+ this.translationLanguageId = languageId;
437
+ };
438
+ updateTranslation = () => void 0;
439
+ getTranslationChanges = () => void 0;
440
+ };
441
+ function createWeaverseNextRuntime(config) {
442
+ let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext);
443
+ let page = getRenderablePage(config.data);
444
+ let runtimeWindow = getRuntimeWindow();
445
+ let existing = runtimeWindow?.__weaverses?.[page.id];
446
+ let requestKey = getRuntimeKey(page.id, requestInfo);
447
+ if (existing?.__weaverseNextRequestKey === requestKey) {
448
+ let nextIsDesignMode = config.client?.requestContext?.isDesignMode ?? false;
449
+ if (!(existing.isDesignMode || nextIsDesignMode)) {
450
+ existing.setProjectData(page);
451
+ }
452
+ existing.dataContext = resolveDataContext(config);
453
+ existing.requestInfo = requestInfo;
454
+ existing.projectId = resolveProjectId(config.client, config.data, page.id);
455
+ existing.isDesignMode = nextIsDesignMode;
456
+ existing.isPreviewMode = config.client?.requestContext?.isPreviewMode ?? false;
457
+ existing.isRevisionPreview = config.client?.requestContext?.isRevisionPreview ?? false;
458
+ existing.sectionType = config.client?.requestContext?.sectionType;
459
+ existing.weaverseHost = getConfigString(config.data.configs, "weaverseHost") ?? existing.weaverseHost;
460
+ existing.weaverseVersion = getConfigString(config.data.configs, "weaverseVersion") ?? existing.weaverseVersion;
461
+ existing.internal.pageAssignment = config.data.pageAssignment;
462
+ existing.internal.project = config.data.project;
463
+ if (config.navigate) {
464
+ existing.internal.navigate = config.navigate;
465
+ }
466
+ if (config.revalidate) {
467
+ existing.internal.revalidate = config.revalidate;
468
+ }
469
+ if (config.themeSettingsStore) {
470
+ existing.internal.themeSettingsStore = config.themeSettingsStore;
471
+ }
472
+ registerRuntime(existing);
473
+ return existing;
474
+ }
475
+ let runtime = new WeaverseNextRuntime(config);
476
+ runtime.__weaverseNextRequestKey = requestKey;
477
+ registerRuntime(runtime);
478
+ return runtime;
479
+ }
480
+ function bindWeaverseNextStudioRuntime(runtime) {
481
+ if (!runtime.isDesignMode) {
482
+ return false;
483
+ }
484
+ let studio = getRuntimeWindow()?.weaverseStudio;
485
+ if (!studio) {
486
+ return false;
487
+ }
488
+ let boundRuntime = runtime;
489
+ if (!boundRuntime.__weaverseNextStudioBound) {
490
+ studio.init?.(runtime);
491
+ boundRuntime.__weaverseNextStudioBound = true;
492
+ return true;
493
+ }
494
+ studio.refreshStudio?.({
495
+ data: runtime.data,
496
+ pageId: runtime.pageId,
497
+ requestInfo: runtime.requestInfo
498
+ });
499
+ return true;
500
+ }
501
+ var STUDIO_BIND_RETRY_INTERVAL_MS = 50;
502
+ var STUDIO_BIND_MAX_ATTEMPTS = 100;
503
+ function WeaverseNextStudioBridge(props) {
504
+ let { runtime, navigate, revalidate } = props;
505
+ let lastRuntimeRef = react$1.useRef(runtime);
506
+ let lastRuntimeDataRef = react$1.useRef(runtime.data);
507
+ let lastRequestInfoRef = react$1.useRef(runtime.requestInfo);
508
+ react$1.useEffect(() => {
509
+ if (navigate) {
510
+ runtime.internal.navigate = navigate;
511
+ }
512
+ if (revalidate) {
513
+ runtime.internal.revalidate = revalidate;
514
+ }
515
+ if (bindWeaverseNextStudioRuntime(runtime) || !runtime.isDesignMode) {
516
+ return;
517
+ }
518
+ let attempts = 0;
519
+ let interval = window.setInterval(() => {
520
+ attempts += 1;
521
+ if (bindWeaverseNextStudioRuntime(runtime) || attempts >= STUDIO_BIND_MAX_ATTEMPTS) {
522
+ window.clearInterval(interval);
523
+ }
524
+ }, STUDIO_BIND_RETRY_INTERVAL_MS);
525
+ return () => window.clearInterval(interval);
526
+ }, [runtime, navigate, revalidate]);
527
+ react$1.useEffect(() => {
528
+ if (lastRuntimeRef.current !== runtime) {
529
+ lastRuntimeRef.current = runtime;
530
+ lastRuntimeDataRef.current = runtime.data;
531
+ lastRequestInfoRef.current = runtime.requestInfo;
532
+ return;
533
+ }
534
+ if (lastRuntimeDataRef.current === runtime.data && lastRequestInfoRef.current === runtime.requestInfo) {
535
+ return;
536
+ }
537
+ lastRuntimeDataRef.current = runtime.data;
538
+ lastRequestInfoRef.current = runtime.requestInfo;
539
+ bindWeaverseNextStudioRuntime(runtime);
540
+ }, [runtime, runtime.data, runtime.requestInfo]);
541
+ return null;
542
+ }
543
+ var EMPTY_DATA_CONTEXT = {};
544
+ function getRenderablePage2(data) {
251
545
  let page = data?.page;
252
546
  if (!page) {
253
547
  return null;
@@ -260,30 +554,136 @@ var WeaverseNextRenderer = react$1.memo(function WeaverseNextRendererComponent(p
260
554
  let client = props.client ?? context?.client;
261
555
  let data = props.data ?? client?.data ?? null;
262
556
  let dataContext = props.dataContext ?? client?.dataContext ?? EMPTY_DATA_CONTEXT;
263
- let page = react$1.useMemo(() => getRenderablePage(data), [data]);
557
+ let page = react$1.useMemo(() => getRenderablePage2(data), [data]);
264
558
  let weaverse = react$1.useMemo(() => {
265
559
  if (!page) {
266
560
  return null;
267
561
  }
268
- let instance = new react.Weaverse({
269
- projectId: client?.projectId || page.id || "weaverse-next",
270
- data: page
562
+ return createWeaverseNextRuntime({
563
+ client,
564
+ data: { ...data, page },
565
+ dataContext,
566
+ themeSettingsStore: context?.themeSettingsStore
271
567
  });
272
- instance.dataContext = dataContext;
273
- instance.isDesignMode = client?.requestContext?.isDesignMode ?? false;
274
- return instance;
275
- }, [
276
- client?.projectId,
277
- client?.requestContext?.isDesignMode,
278
- page,
279
- dataContext
280
- ]);
568
+ }, [client, page, data, dataContext, context?.themeSettingsStore]);
281
569
  if (!weaverse) {
282
570
  return null;
283
571
  }
284
- return /* @__PURE__ */ jsxRuntime.jsx(react.WeaverseRoot, { context: weaverse });
572
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
573
+ /* @__PURE__ */ jsxRuntime.jsx(react.WeaverseRoot, { context: weaverse }),
574
+ /* @__PURE__ */ jsxRuntime.jsx(WeaverseNextStudioBridge, { runtime: weaverse })
575
+ ] });
285
576
  });
286
577
 
578
+ // src/studio-script-src.ts
579
+ var TRUSTED_STUDIO_DOMAINS = ["weaverse.io", "weaverse.dev"];
580
+ function isLoopbackHostname(hostname) {
581
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname.endsWith(".localhost");
582
+ }
583
+ function normalizeOrigin(value) {
584
+ if (!value) {
585
+ return null;
586
+ }
587
+ try {
588
+ return new URL(value).origin;
589
+ } catch {
590
+ return null;
591
+ }
592
+ }
593
+ function isTrustedStudioOrigin(origin, storefrontHostname) {
594
+ let parsed;
595
+ try {
596
+ parsed = new URL(origin);
597
+ } catch {
598
+ return false;
599
+ }
600
+ let { hostname, protocol } = parsed;
601
+ if (isLoopbackHostname(storefrontHostname) && isLoopbackHostname(hostname)) {
602
+ return protocol === "http:" || protocol === "https:";
603
+ }
604
+ if (protocol !== "https:") {
605
+ return false;
606
+ }
607
+ return TRUSTED_STUDIO_DOMAINS.some(
608
+ (domain) => hostname === domain || hostname.endsWith(`.${domain}`)
609
+ );
610
+ }
611
+ function toSearchParams(context) {
612
+ if (context?.searchParams) {
613
+ return new URLSearchParams(context.searchParams);
614
+ }
615
+ if (context?.url) {
616
+ let url = typeof context.url === "string" ? new URL(context.url, "http://localhost") : context.url;
617
+ return new URLSearchParams(url.searchParams);
618
+ }
619
+ return new URLSearchParams();
620
+ }
621
+ function last(searchParams, key) {
622
+ let values = searchParams.getAll(key);
623
+ return values.length ? values.at(-1) ?? null : null;
624
+ }
625
+ function resolveWeaverseNextStudioScriptSrc(context, options = {}) {
626
+ let searchParams = toSearchParams(context);
627
+ let isDesignMode = context?.isDesignMode ?? last(searchParams, "isDesignMode") === "true";
628
+ let isPreviewMode = context?.isPreviewMode ?? last(searchParams, "isPreviewMode") === "true";
629
+ let isRevisionPreview = context?.isRevisionPreview ?? Boolean(last(searchParams, "__revisionId"));
630
+ if (!(isDesignMode || isPreviewMode || isRevisionPreview)) {
631
+ return null;
632
+ }
633
+ let origin = normalizeOrigin(last(searchParams, "weaverseHost"));
634
+ if (!origin) {
635
+ return null;
636
+ }
637
+ let storefrontHostname = options.storefrontHostname ?? "";
638
+ if (!isTrustedStudioOrigin(origin, storefrontHostname)) {
639
+ return null;
640
+ }
641
+ let framework = options.framework ?? "hydrogen";
642
+ let bundle = isDesignMode ? "index.js" : "preview.js";
643
+ let version = last(searchParams, "weaverseVersion");
644
+ let url = `${origin}/static/studio/${framework}/${bundle}`;
645
+ return version ? `${url}?v=${encodeURIComponent(version)}` : url;
646
+ }
647
+
648
+ // src/studio-connect.tsx
649
+ var loadedScriptSrcs = /* @__PURE__ */ new Set();
650
+ function loadScript(src) {
651
+ if (typeof document === "undefined") {
652
+ return;
653
+ }
654
+ if (loadedScriptSrcs.has(src)) {
655
+ return;
656
+ }
657
+ let existing = document.querySelector(`script[src="${src}"]`);
658
+ if (existing) {
659
+ loadedScriptSrcs.add(src);
660
+ return;
661
+ }
662
+ let script = document.createElement("script");
663
+ script.async = true;
664
+ script.src = src;
665
+ document.head.appendChild(script);
666
+ loadedScriptSrcs.add(src);
667
+ }
668
+ function WeaverseNextStudioConnect(props) {
669
+ react$1.useEffect(() => {
670
+ let context = props.context;
671
+ if (!context && typeof window !== "undefined") {
672
+ context = {
673
+ searchParams: new URLSearchParams(window.location.search),
674
+ url: window.location.href
675
+ };
676
+ }
677
+ let src = resolveWeaverseNextStudioScriptSrc(context, {
678
+ storefrontHostname: props.storefrontHostname ?? (typeof window === "undefined" ? void 0 : window.location.hostname)
679
+ });
680
+ if (src) {
681
+ loadScript(src);
682
+ }
683
+ }, [props.context, props.storefrontHostname]);
684
+ return null;
685
+ }
686
+
287
687
  Object.defineProperty(exports, "isBrowser", {
288
688
  enumerable: true,
289
689
  get: function () { return react.isBrowser; }
@@ -314,7 +714,15 @@ Object.defineProperty(exports, "createSchema", {
314
714
  });
315
715
  exports.WeaverseNextProvider = WeaverseNextProvider;
316
716
  exports.WeaverseNextRenderer = WeaverseNextRenderer;
717
+ exports.WeaverseNextRuntime = WeaverseNextRuntime;
718
+ exports.WeaverseNextStudioBridge = WeaverseNextStudioBridge;
719
+ exports.WeaverseNextStudioConnect = WeaverseNextStudioConnect;
720
+ exports.bindWeaverseNextStudioRuntime = bindWeaverseNextStudioRuntime;
721
+ exports.buildWeaverseNextRequestInfo = buildWeaverseNextRequestInfo;
317
722
  exports.createWeaverseNextClient = createWeaverseNextClient;
723
+ exports.createWeaverseNextRuntime = createWeaverseNextRuntime;
724
+ exports.createWeaverseNextThemeSettingsStore = createWeaverseNextThemeSettingsStore;
725
+ exports.resolveWeaverseNextStudioScriptSrc = resolveWeaverseNextStudioScriptSrc;
318
726
  exports.runWeaverseComponentLoaders = runWeaverseComponentLoaders;
319
727
  exports.useThemeSettings = useThemeSettings;
320
728
  exports.useWeaverseCommerce = useWeaverseCommerce;