@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
|
@@ -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();
|