@jx3box/jx3box-ui 2.4.4 → 2.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-ui",
3
- "version": "2.4.4",
3
+ "version": "2.4.5",
4
4
  "description": "JX3BOX Vue3 UI",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -18,6 +18,106 @@ const EMBEDDED_SURFACES = new Set(["app", "miniprogram", "pc_game", "mobile_game
18
18
  const COMMON_HEADER_INSTALLATIONS = new WeakMap();
19
19
  const COMMON_HEADER_TRAFFIC_PROJECT = "jx3box-ui";
20
20
 
21
+ function normalizeRoutePath(value) {
22
+ let path = String(value || "")
23
+ .split(/[?#]/, 1)[0]
24
+ .trim();
25
+ if (!path) return "";
26
+ if (!path.startsWith("/")) path = `/${path}`;
27
+ path = path.replace(/\/{2,}/g, "/");
28
+ return path.length > 1 ? path.replace(/\/+$/, "") : path;
29
+ }
30
+
31
+ function normalizeVueRouteTemplate(value) {
32
+ const path = normalizeRoutePath(value);
33
+ if (!path || path === "/") return path;
34
+ return `/${path
35
+ .slice(1)
36
+ .split("/")
37
+ .map(function (segment) {
38
+ if (!segment.startsWith(":")) return segment;
39
+ const name = segment.slice(1).match(/^[A-Za-z_][A-Za-z0-9_]*/)?.[0];
40
+ if (!name) return segment;
41
+ // Backend rules own parameter validation. Vue Router custom regexes
42
+ // such as :id(\d+) are local matching details and must normalize to
43
+ // the registered cross-client template :id.
44
+ return `:${name}${segment.endsWith("?") ? "?" : ""}`;
45
+ })
46
+ .join("/")}`;
47
+ }
48
+
49
+ function routeTemplate(route) {
50
+ const source = route || {};
51
+ const meta = source.meta && typeof source.meta === "object" ? source.meta : {};
52
+ const analytics = meta.analytics && typeof meta.analytics === "object" ? meta.analytics : {};
53
+ const matched = Array.isArray(source.matched) ? source.matched : [];
54
+ const record = matched.length ? matched[matched.length - 1] : null;
55
+ return normalizeVueRouteTemplate(
56
+ analytics.route_pattern || analytics.routePattern || (record && record.path) || source.path || ""
57
+ );
58
+ }
59
+
60
+ function routerBase(router) {
61
+ return normalizeRoutePath(router?.options?.history?.base || router?.history?.base || "/") || "/";
62
+ }
63
+
64
+ function resolveAbsoluteRoutePattern(router, route, runtime) {
65
+ const pattern = routeTemplate(route);
66
+ if (!pattern) return "";
67
+ const base = routerBase(router);
68
+ if (base === "/" || pattern === base || pattern.startsWith(`${base}/`)) return pattern;
69
+
70
+ // Multi-entry PC projects mount their Router below paths such as /macro or /pet.
71
+ // Vue Router exposes only / and /:id inside that base, so retain the route
72
+ // template while restoring its public prefix. location.pathname is used only
73
+ // to verify that this Router currently owns the browser path; real IDs never
74
+ // replace the template or enter the long-term page key.
75
+ const pathname = normalizeRoutePath(runtime?.location?.pathname || "");
76
+ if (pathname && pathname !== base && !pathname.startsWith(`${base}/`)) return pattern;
77
+ return pattern === "/" ? base : `${base}${pattern}`;
78
+ }
79
+
80
+ function createAbsoluteRouteRouter(router, runtime) {
81
+ if (!router || typeof router.afterEach !== "function") return router;
82
+ const adaptRoute = function (route) {
83
+ if (!route) return route;
84
+ const routePattern = resolveAbsoluteRoutePattern(router, route, runtime);
85
+ if (!routePattern) return route;
86
+ const meta = route.meta && typeof route.meta === "object" ? route.meta : {};
87
+ const analytics = meta.analytics && typeof meta.analytics === "object" ? meta.analytics : {};
88
+ return Object.assign({}, route, {
89
+ meta: Object.assign({}, meta, {
90
+ analytics: Object.assign({}, analytics, { route_pattern: routePattern }),
91
+ }),
92
+ });
93
+ };
94
+ const adapter = Object.create(router);
95
+ adapter.afterEach = function (handler) {
96
+ return router.afterEach(function (to, from, failure) {
97
+ return handler(adaptRoute(to), adaptRoute(from), failure);
98
+ });
99
+ };
100
+ if (typeof router.isReady === "function") adapter.isReady = router.isReady.bind(router);
101
+ const currentRoute = router.currentRoute;
102
+ if (currentRoute && typeof currentRoute === "object" && "value" in currentRoute) {
103
+ adapter.currentRoute = {};
104
+ Object.defineProperty(adapter.currentRoute, "value", {
105
+ enumerable: true,
106
+ get: function () {
107
+ return adaptRoute(currentRoute.value);
108
+ },
109
+ });
110
+ } else {
111
+ Object.defineProperty(adapter, "currentRoute", {
112
+ enumerable: true,
113
+ get: function () {
114
+ return adaptRoute(router.currentRoute);
115
+ },
116
+ });
117
+ }
118
+ return adapter;
119
+ }
120
+
21
121
  function queryValue(runtime, key) {
22
122
  const location = (runtime && runtime.location) || {};
23
123
  const sources = [location.search, String(location.hash || "").split("?")[1]];
@@ -465,12 +565,13 @@ function installCommonHeaderTrafficAnalytics(options) {
465
565
  const surface = settings.surface || "pc_web";
466
566
  if (surface !== "pc_web" && surface !== "mobile_web") return Promise.resolve(null);
467
567
  const project = COMMON_HEADER_TRAFFIC_PROJECT;
568
+ const analyticsRouter = createAbsoluteRouteRouter(router, runtime);
468
569
 
469
570
  const task = Promise.resolve(typeof router.isReady === "function" ? router.isReady() : undefined)
470
571
  .then(function () {
471
572
  const collector = createJx3boxTrafficAnalytics({
472
573
  runtime,
473
- router,
574
+ router: analyticsRouter,
474
575
  project,
475
576
  product: "jx3box",
476
577
  client: surface,
@@ -497,8 +598,10 @@ export {
497
598
  DEFAULT_CONFIG_ENDPOINT,
498
599
  DEFAULT_QUEUE_STORAGE_KEY,
499
600
  createJx3boxTrafficAnalytics,
601
+ createAbsoluteRouteRouter,
500
602
  installCommonHeaderTrafficAnalytics,
501
603
  normalizeRecipientDomain,
502
604
  normalizeTrafficPermission,
503
605
  resolvePcTrafficGameClient,
606
+ resolveAbsoluteRoutePattern,
504
607
  };
@@ -24,10 +24,12 @@ const { createEventQueue, createQueueStorage, createTrafficSink } = require("@jx
24
24
  const {
25
25
  DEFAULT_BLOCK_STORAGE_KEY,
26
26
  DEFAULT_QUEUE_STORAGE_KEY,
27
+ createAbsoluteRouteRouter,
27
28
  createJx3boxTrafficAnalytics,
28
29
  normalizeRecipientDomain,
29
30
  normalizeTrafficPermission,
30
31
  resolvePcTrafficGameClient,
32
+ resolveAbsoluteRoutePattern,
31
33
  } = require("../src/utils/traffic-analytics.js");
32
34
  const trafficSource = fs.readFileSync(path.resolve(__dirname, "../src/utils/traffic-analytics.js"), "utf8");
33
35
 
@@ -91,6 +93,39 @@ function createRouter() {
91
93
  };
92
94
  }
93
95
 
96
+ test("CommonHeader restores a multi-entry Router base without exposing real path IDs", () => {
97
+ const router = createRouter();
98
+ router.options = { history: { base: "/macro/" } };
99
+ const runtime = { location: { pathname: "/macro/123" } };
100
+ const detail = {
101
+ name: "single",
102
+ matched: [{ path: "/:id(\\d+)" }],
103
+ meta: {},
104
+ params: { id: "123" },
105
+ };
106
+ assert.equal(resolveAbsoluteRoutePattern(router, detail, runtime), "/macro/:id");
107
+ assert.equal(resolveAbsoluteRoutePattern(router, { matched: [{ path: "/" }], meta: {} }, runtime), "/macro");
108
+
109
+ const adapted = createAbsoluteRouteRouter(router, runtime);
110
+ let received = null;
111
+ adapted.afterEach((to) => {
112
+ received = to;
113
+ });
114
+ router.navigate(detail);
115
+ assert.equal(received.meta.analytics.route_pattern, "/macro/:id");
116
+ assert.equal(received.meta.analytics.route_pattern.includes("123"), false);
117
+ });
118
+
119
+ test("CommonHeader does not duplicate an already absolute route template", () => {
120
+ const router = createRouter();
121
+ router.options = { history: { base: "/pet" } };
122
+ const runtime = { location: { pathname: "/pet/456" } };
123
+ assert.equal(
124
+ resolveAbsoluteRoutePattern(router, { matched: [{ path: "/pet/:id" }], meta: {} }, runtime),
125
+ "/pet/:id"
126
+ );
127
+ });
128
+
94
129
  function createRuntime(options) {
95
130
  const settings = options || {};
96
131
  const listeners = new Map();