@archie/devtools 0.1.11 → 0.1.13

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.
@@ -10236,7 +10236,7 @@ function isPreviewOrigin(origin) {
10236
10236
  var DEFAULT_SCRIPT_ID = "archie-inspector-script";
10237
10237
  var R2_INSPECTOR_BASE_URL = "https://pub-bf238c278d9644b39ec34ecaca1d315c.r2.dev";
10238
10238
  function readBuildVersion() {
10239
- return "0.1.11" ? "0.1.11" : null;
10239
+ return "0.1.13" ? "0.1.13" : null;
10240
10240
  }
10241
10241
  var REMOTE_INSPECTOR_VERSION = readBuildVersion();
10242
10242
  var isRemoteInspectorUrlVersioned = REMOTE_INSPECTOR_VERSION !== null;
@@ -10316,25 +10316,238 @@ function injectInspector(opts = {}) {
10316
10316
  document.head.appendChild(script);
10317
10317
  }
10318
10318
 
10319
+ // src/client/route-listener/domSnapshot.ts
10320
+ var STRIP_SELECTOR = [
10321
+ "script",
10322
+ "noscript",
10323
+ "iframe",
10324
+ "[data-inspector-badge]",
10325
+ "[data-inspector-context-menu]",
10326
+ "#archie-inspector-styles"
10327
+ ].join(",");
10328
+ var PALETTE_SELECTOR = "#archie-design-palette";
10329
+ var CSS_FETCH_TIMEOUT_MS = 2e3;
10330
+ var IDLE_TIMEOUT_MS = 500;
10331
+ var CSS_URL_RE = /url\(\s*(['"]?)([^'")]+)\1\s*\)/g;
10332
+ function absolutizeCssUrls(css, baseHref) {
10333
+ return css.replace(CSS_URL_RE, (full, _quote, raw) => {
10334
+ if (!raw || raw.startsWith("data:") || raw.startsWith("#")) return full;
10335
+ try {
10336
+ return `url(${new URL(raw, baseHref).href})`;
10337
+ } catch {
10338
+ return full;
10339
+ }
10340
+ });
10341
+ }
10342
+ function isExcludedSheet(sheet) {
10343
+ const owner = sheet.ownerNode;
10344
+ return !!owner?.matches?.(`${STRIP_SELECTOR},${PALETTE_SELECTOR}`);
10345
+ }
10346
+ async function fetchCssText(href) {
10347
+ const controller = new AbortController();
10348
+ const timer = setTimeout(() => controller.abort(), CSS_FETCH_TIMEOUT_MS);
10349
+ try {
10350
+ const response = await fetch(href, {
10351
+ mode: "cors",
10352
+ signal: controller.signal
10353
+ });
10354
+ return response.ok ? await response.text() : "";
10355
+ } catch {
10356
+ return "";
10357
+ } finally {
10358
+ clearTimeout(timer);
10359
+ }
10360
+ }
10361
+ async function collectSheetCss(sheet, inlinedHrefs) {
10362
+ if (sheet.disabled || isExcludedSheet(sheet)) return "";
10363
+ const baseHref = sheet.href || document.baseURI;
10364
+ let css = "";
10365
+ try {
10366
+ css = Array.from(sheet.cssRules, (rule) => rule.cssText).join("\n");
10367
+ } catch {
10368
+ css = sheet.href ? await fetchCssText(sheet.href) : "";
10369
+ }
10370
+ if (!css) {
10371
+ const owner = sheet.ownerNode;
10372
+ if (owner?.tagName === "STYLE") css = owner.textContent ?? "";
10373
+ }
10374
+ if (!css) return "";
10375
+ if (sheet.href) inlinedHrefs.add(sheet.href);
10376
+ css = absolutizeCssUrls(css, baseHref);
10377
+ const media = sheet.media?.mediaText;
10378
+ return media && media !== "all" ? `@media ${media}{${css}}` : css;
10379
+ }
10380
+ async function collectDocumentCss(inlinedHrefs) {
10381
+ const sheets = Array.from(document.styleSheets);
10382
+ const parts = await Promise.all(
10383
+ sheets.map((sheet) => collectSheetCss(sheet, inlinedHrefs))
10384
+ );
10385
+ return parts.filter(Boolean).join("\n");
10386
+ }
10387
+ function waitForIdle() {
10388
+ return new Promise((resolve) => {
10389
+ const w = window;
10390
+ if (typeof w.requestIdleCallback === "function") {
10391
+ w.requestIdleCallback(() => resolve(), { timeout: IDLE_TIMEOUT_MS });
10392
+ } else {
10393
+ setTimeout(resolve, 50);
10394
+ }
10395
+ });
10396
+ }
10397
+ async function captureDomSnapshot() {
10398
+ if (document.fonts?.ready) {
10399
+ await document.fonts.ready;
10400
+ }
10401
+ await waitForIdle();
10402
+ const inlinedHrefs = /* @__PURE__ */ new Set();
10403
+ const css = await collectDocumentCss(inlinedHrefs);
10404
+ const clone = document.documentElement.cloneNode(true);
10405
+ clone.querySelectorAll(`${STRIP_SELECTOR}, style:not(${PALETTE_SELECTOR})`).forEach((el) => el.remove());
10406
+ clone.querySelectorAll('link[rel~="stylesheet"]').forEach((link) => {
10407
+ const href = link.href;
10408
+ if (inlinedHrefs.has(href)) {
10409
+ link.remove();
10410
+ } else {
10411
+ link.setAttribute("href", href);
10412
+ }
10413
+ });
10414
+ for (const el of [clone, ...Array.from(clone.querySelectorAll("*"))]) {
10415
+ for (const attr of Array.from(el.attributes)) {
10416
+ if (/^on[a-z]+$/i.test(attr.name)) {
10417
+ el.removeAttribute(attr.name);
10418
+ }
10419
+ }
10420
+ }
10421
+ let head = clone.querySelector("head");
10422
+ if (!head) {
10423
+ head = document.createElement("head");
10424
+ clone.insertBefore(head, clone.firstChild);
10425
+ }
10426
+ const style = document.createElement("style");
10427
+ style.textContent = css;
10428
+ head.insertBefore(style, head.firstChild);
10429
+ if (!head.querySelector("base")) {
10430
+ const base = document.createElement("base");
10431
+ base.setAttribute("href", document.baseURI);
10432
+ head.insertBefore(base, head.firstChild);
10433
+ }
10434
+ return {
10435
+ html: "<!doctype html>" + clone.outerHTML,
10436
+ path: location.pathname + location.search + location.hash,
10437
+ viewport: { width: window.innerWidth, height: window.innerHeight },
10438
+ capturedAt: Date.now()
10439
+ };
10440
+ }
10441
+
10319
10442
  // src/client/route-listener/routeListener.ts
10320
- function extractAllRoutes(routes, parentPath = "") {
10443
+ var GUARD_NAME_RE = /guard|protect|require|auth(?!or)/i;
10444
+ var GUARD_PRIVATE_RE = /\bprivate|Private(?=[A-Z]|$)/;
10445
+ var NON_GUARD_SUFFIX_RE = /(Layout|Provider|Context|Page|Form|Screen|View)$/;
10446
+ function isGuardName(name) {
10447
+ return !NON_GUARD_SUFFIX_RE.test(name) && (GUARD_NAME_RE.test(name) || GUARD_PRIVATE_RE.test(name));
10448
+ }
10449
+ var GUARD_PROP_NAMES = [
10450
+ "roles",
10451
+ "allowedRoles",
10452
+ "requiredRoles",
10453
+ "permission",
10454
+ "permissions"
10455
+ ];
10456
+ var MAX_ELEMENT_DEPTH = 6;
10457
+ function typeName(type) {
10458
+ if (typeof type === "string") return type;
10459
+ if (typeof type === "function") {
10460
+ const fn = type;
10461
+ return fn.displayName || fn.name || null;
10462
+ }
10463
+ if (type && typeof type === "object") {
10464
+ const obj = type;
10465
+ return obj.displayName || typeName(obj.render) || typeName(obj.type);
10466
+ }
10467
+ return null;
10468
+ }
10469
+ function isElementLike(node) {
10470
+ return !!node && typeof node === "object" && "type" in node && "props" in node;
10471
+ }
10472
+ function literalRoles(value) {
10473
+ if (typeof value === "string") return [value];
10474
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
10475
+ return value;
10476
+ }
10477
+ return void 0;
10478
+ }
10479
+ function collectGuards(node, depth, out) {
10480
+ if (depth > MAX_ELEMENT_DEPTH || node == null) return;
10481
+ if (Array.isArray(node)) {
10482
+ for (const child of node) collectGuards(child, depth, out);
10483
+ return;
10484
+ }
10485
+ if (!isElementLike(node)) return;
10486
+ const props = node.props ?? {};
10487
+ const name = typeName(node.type);
10488
+ const roleProps = GUARD_PROP_NAMES.filter((p) => p in props);
10489
+ const isNamedGuard = typeof node.type !== "string" && !!name && isGuardName(name);
10490
+ if (isNamedGuard || roleProps.length > 0) {
10491
+ const roles = roleProps.flatMap((p) => literalRoles(props[p]) ?? []);
10492
+ out.push({
10493
+ componentName: name ?? "Anonymous",
10494
+ ...roles.length > 0 ? { roles } : {}
10495
+ });
10496
+ }
10497
+ collectGuards(props.children, depth + 1, out);
10498
+ }
10499
+ function routeComponentName(route) {
10500
+ if (route.Component) return typeName(route.Component);
10501
+ if (isElementLike(route.element)) return typeName(route.element.type);
10502
+ return null;
10503
+ }
10504
+ function extractAllRoutes(routes, parentPath = "", parentId = null, inheritedGuards = []) {
10321
10505
  const result = [];
10322
10506
  for (const route of routes) {
10323
10507
  const path = route.path ? (parentPath + "/" + route.path).replace(/\/+/g, "/") : parentPath || "/";
10324
- if (route.path !== void 0 || route.index) {
10508
+ const hasChildren = !!(route.children && route.children.length > 0);
10509
+ const isLayout = !route.path && !route.index && hasChildren;
10510
+ const own = [];
10511
+ collectGuards(route.element, 0, own);
10512
+ if (route.path !== void 0 || route.index || isLayout) {
10325
10513
  result.push({
10326
10514
  id: route.id,
10327
10515
  path: route.index ? parentPath || "/" : path,
10328
- hasChildren: !!(route.children && route.children.length > 0)
10516
+ hasChildren,
10517
+ parentId,
10518
+ index: !!route.index,
10519
+ isLayout,
10520
+ componentName: routeComponentName(route),
10521
+ guardEvidence: [
10522
+ ...own.map((g) => ({ ...g, source: "self" })),
10523
+ ...inheritedGuards
10524
+ ]
10329
10525
  });
10330
10526
  }
10331
10527
  if (route.children) {
10332
- result.push(...extractAllRoutes(route.children, path));
10528
+ const forChildren = [
10529
+ ...own.map(
10530
+ (g) => ({
10531
+ ...g,
10532
+ source: "ancestor",
10533
+ ...route.id !== void 0 ? { ancestorId: route.id } : {}
10534
+ })
10535
+ ),
10536
+ ...inheritedGuards
10537
+ ];
10538
+ result.push(
10539
+ ...extractAllRoutes(
10540
+ route.children,
10541
+ path,
10542
+ route.id ?? parentId,
10543
+ forChildren
10544
+ )
10545
+ );
10333
10546
  }
10334
10547
  }
10335
10548
  return result;
10336
10549
  }
10337
- function broadcastRouteState(target, state, allRoutes) {
10550
+ function broadcastRouteState(target, state, routes) {
10338
10551
  try {
10339
10552
  const message = {
10340
10553
  type: "ARCHIE_ROUTE_UPDATE",
@@ -10347,7 +10560,9 @@ function broadcastRouteState(target, state, allRoutes) {
10347
10560
  pathname: m.pathname,
10348
10561
  params: m.params
10349
10562
  })),
10350
- allRoutes,
10563
+ // Layouts stay out of allRoutes so consumers that predate them don't list duplicates.
10564
+ allRoutes: routes.filter((r) => !r.isLayout),
10565
+ layoutRoutes: routes.filter((r) => r.isLayout),
10351
10566
  timestamp: Date.now()
10352
10567
  }
10353
10568
  };
@@ -10355,21 +10570,174 @@ function broadcastRouteState(target, state, allRoutes) {
10355
10570
  } catch {
10356
10571
  }
10357
10572
  }
10358
- function setupArchieRouteListener(router) {
10573
+ var SETTLE_QUIET_MS = 400;
10574
+ var SETTLE_TIMEOUT_MS = 8e3;
10575
+ var SETTLE_POLL_MS = 50;
10576
+ function readPackageVersion() {
10577
+ return true ? "0.1.13" : "unknown";
10578
+ }
10579
+ var DEFAULT_ARCHIE_COMMAND_ORIGINS = getAllowedOrigins(true);
10580
+ function originPatternToRegExp(pattern) {
10581
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/:\*$/, ":\\d+").replace(/\*/g, "[a-z0-9.-]+");
10582
+ return new RegExp(`^${escaped}$`, "i");
10583
+ }
10584
+ function isAllowedCommandOrigin(origin, allowed) {
10585
+ return allowed.some((pattern) => originPatternToRegExp(pattern).test(origin));
10586
+ }
10587
+ function postToTarget(target, message, targetOrigin = "*") {
10588
+ try {
10589
+ target.postMessage(message, targetOrigin);
10590
+ } catch {
10591
+ }
10592
+ }
10593
+ function isRouterBusy(router) {
10594
+ const { navigation, fetchers } = router.state;
10595
+ if (navigation && navigation.state !== "idle") return true;
10596
+ if (fetchers) {
10597
+ for (const fetcher of fetchers.values()) {
10598
+ if (fetcher.state !== "idle") return true;
10599
+ }
10600
+ }
10601
+ return false;
10602
+ }
10603
+ function waitForSettled(router, navigation) {
10604
+ return new Promise((resolve) => {
10605
+ const startedAt = Date.now();
10606
+ let lastMutationAt = Date.now();
10607
+ let navigationDone = false;
10608
+ const observer = new MutationObserver(() => {
10609
+ lastMutationAt = Date.now();
10610
+ });
10611
+ observer.observe(document, {
10612
+ subtree: true,
10613
+ childList: true,
10614
+ attributes: true,
10615
+ characterData: true
10616
+ });
10617
+ navigation.then(
10618
+ () => {
10619
+ navigationDone = true;
10620
+ },
10621
+ () => {
10622
+ navigationDone = true;
10623
+ }
10624
+ );
10625
+ const finish = (settled) => {
10626
+ clearInterval(timer);
10627
+ observer.disconnect();
10628
+ resolve(settled);
10629
+ };
10630
+ const timer = setInterval(() => {
10631
+ const now = Date.now();
10632
+ if (navigationDone && !isRouterBusy(router) && now - lastMutationAt >= SETTLE_QUIET_MS) {
10633
+ finish(true);
10634
+ } else if (now - startedAt >= SETTLE_TIMEOUT_MS) {
10635
+ finish(false);
10636
+ }
10637
+ }, SETTLE_POLL_MS);
10638
+ });
10639
+ }
10640
+ function pathnameOf(path) {
10641
+ return path.split(/[?#]/, 1)[0];
10642
+ }
10643
+ async function handleNavigate(router, target, targetOrigin, data) {
10644
+ if (typeof router.navigate !== "function") {
10645
+ const { location: location3 } = router.state;
10646
+ postToTarget(target, {
10647
+ type: "ARCHIE_NAVIGATE_RESULT",
10648
+ requestId: data.requestId,
10649
+ requestedPath: data.path,
10650
+ finalPath: location3.pathname + location3.search + location3.hash,
10651
+ matchedRouteIds: [],
10652
+ redirected: false,
10653
+ settled: false
10654
+ }, targetOrigin);
10655
+ return;
10656
+ }
10657
+ let navigation = Promise.resolve();
10658
+ try {
10659
+ navigation = Promise.resolve(
10660
+ router.navigate(data.path, { replace: data.replace })
10661
+ );
10662
+ } catch {
10663
+ navigation = Promise.resolve();
10664
+ }
10665
+ const settled = await waitForSettled(router, navigation);
10666
+ const { location: location2, matches } = router.state;
10667
+ postToTarget(target, {
10668
+ type: "ARCHIE_NAVIGATE_RESULT",
10669
+ requestId: data.requestId,
10670
+ requestedPath: data.path,
10671
+ finalPath: location2.pathname + location2.search + location2.hash,
10672
+ matchedRouteIds: matches.map((m) => m.route.id).filter((id) => typeof id === "string"),
10673
+ redirected: location2.pathname !== pathnameOf(data.path),
10674
+ settled
10675
+ }, targetOrigin);
10676
+ }
10677
+ async function handleSnapshot(target, targetOrigin, requestId) {
10678
+ try {
10679
+ const snapshot = await captureDomSnapshot();
10680
+ postToTarget(target, {
10681
+ type: "ARCHIE_SNAPSHOT_DOM_RESULT",
10682
+ requestId,
10683
+ ...snapshot
10684
+ }, targetOrigin);
10685
+ } catch (error) {
10686
+ postToTarget(target, {
10687
+ type: "ARCHIE_SNAPSHOT_DOM_FAILED",
10688
+ requestId,
10689
+ error: error instanceof Error ? error.message : String(error)
10690
+ }, targetOrigin);
10691
+ }
10692
+ }
10693
+ function broadcastCapabilities(router, target) {
10694
+ postToTarget(target, {
10695
+ type: "ARCHIE_CAPABILITIES",
10696
+ version: readPackageVersion(),
10697
+ capabilities: {
10698
+ navigate: typeof router.navigate === "function",
10699
+ snapshot: true,
10700
+ identityOverride: false
10701
+ }
10702
+ });
10703
+ }
10704
+ function setupArchieRouteListener(router, options = {}) {
10705
+ const allowedOrigins = options.allowedOrigins ?? DEFAULT_ARCHIE_COMMAND_ORIGINS;
10359
10706
  const target = window.parent !== window ? window.parent : window.opener;
10360
10707
  if (!target) {
10361
10708
  return () => {
10362
10709
  };
10363
10710
  }
10364
10711
  broadcastRouteState(target, router.state, extractAllRoutes(router.routes));
10712
+ broadcastCapabilities(router, target);
10365
10713
  const unsubscribe = router.subscribe((state) => {
10366
10714
  const currentRoutes = extractAllRoutes(router.routes);
10367
10715
  broadcastRouteState(target, state, currentRoutes);
10368
10716
  });
10369
10717
  const handleMessage = (event) => {
10370
- if (event.data?.type === "ARCHIE_QUERY_ROUTES") {
10371
- const currentRoutes = extractAllRoutes(router.routes);
10372
- broadcastRouteState(target, router.state, currentRoutes);
10718
+ if (event.source !== target) return;
10719
+ const data = event.data;
10720
+ switch (data?.type) {
10721
+ case "ARCHIE_QUERY_ROUTES":
10722
+ broadcastRouteState(
10723
+ target,
10724
+ router.state,
10725
+ extractAllRoutes(router.routes)
10726
+ );
10727
+ break;
10728
+ case "ARCHIE_QUERY_CAPABILITIES":
10729
+ broadcastCapabilities(router, target);
10730
+ break;
10731
+ case "ARCHIE_NAVIGATE":
10732
+ if (isAllowedCommandOrigin(event.origin, allowedOrigins) && typeof data.requestId === "string" && typeof data.path === "string") {
10733
+ void handleNavigate(router, target, event.origin, data);
10734
+ }
10735
+ break;
10736
+ case "ARCHIE_SNAPSHOT_DOM":
10737
+ if (isAllowedCommandOrigin(event.origin, allowedOrigins) && typeof data.requestId === "string") {
10738
+ void handleSnapshot(target, event.origin, data.requestId);
10739
+ }
10740
+ break;
10373
10741
  }
10374
10742
  };
10375
10743
  window.addEventListener("message", handleMessage);
@@ -10409,7 +10777,8 @@ function installDomMetaSanitizer() {
10409
10777
  function useArchieDevToolsRuntime({
10410
10778
  router,
10411
10779
  inspector,
10412
- inspectorOptions
10780
+ inspectorOptions,
10781
+ routeListenerOrigins
10413
10782
  }) {
10414
10783
  installDomMetaSanitizer();
10415
10784
  const stableOptionsKey = JSON.stringify(inspectorOptions ?? null);
@@ -10419,7 +10788,9 @@ function useArchieDevToolsRuntime({
10419
10788
  if (process.env.NODE_ENV !== "development") {
10420
10789
  return;
10421
10790
  }
10422
- const unsubscribe = setupArchieRouteListener(router);
10791
+ const unsubscribe = setupArchieRouteListener(router, {
10792
+ allowedOrigins: routeListenerOrigins
10793
+ });
10423
10794
  if (inspector !== false) {
10424
10795
  const useLocalInspector = readArchieLocalInspectorFlag();
10425
10796
  injectInspector({
@@ -10437,9 +10808,15 @@ function ArchieDevToolProvider({
10437
10808
  children,
10438
10809
  router,
10439
10810
  inspector,
10440
- inspectorOptions
10811
+ inspectorOptions,
10812
+ routeListenerOrigins
10441
10813
  }) {
10442
- useArchieDevToolsRuntime({ router, inspector, inspectorOptions });
10814
+ useArchieDevToolsRuntime({
10815
+ router,
10816
+ inspector,
10817
+ inspectorOptions,
10818
+ routeListenerOrigins
10819
+ });
10443
10820
  return /* @__PURE__ */ jsx6(DndProvider, { children });
10444
10821
  }
10445
10822
  export {