@abbox/inspector 0.3.0 → 0.4.2
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/browser.d.ts +3 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -3
- package/dist/host/client-bridge.js +1 -1
- package/dist/host/host-bridge.d.ts.map +1 -1
- package/dist/host/host-bridge.js +5 -1
- package/dist/host/origin-aliases.d.ts.map +1 -1
- package/dist/next/abbox-pre-swc-loader.cjs +44 -0
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/webpack-instrumentation.cjs +104 -26
- package/dist/next/with-abbox-inspector.d.ts.map +1 -1
- package/dist/next/with-abbox-inspector.js +4 -0
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/runtime/bootstrap-standby.d.ts +3 -0
- package/dist/runtime/bootstrap-standby.d.ts.map +1 -0
- package/dist/runtime/bootstrap-standby.js +20 -0
- package/dist/runtime/bootstrap.d.ts +5 -2
- package/dist/runtime/bootstrap.d.ts.map +1 -1
- package/dist/runtime/bootstrap.js +59 -12
- package/dist/runtime/inspector.d.ts.map +1 -1
- package/dist/runtime/inspector.js +6 -3
- package/dist/runtime/metadata.d.ts.map +1 -1
- package/dist/runtime/metadata.js +15 -0
- package/dist/runtime/resolve-change-marker.d.ts +12 -0
- package/dist/runtime/resolve-change-marker.d.ts.map +1 -0
- package/dist/runtime/resolve-change-marker.js +93 -0
- package/docs/nextjs-integration.md +2 -1
- package/package.json +4 -2
- package/src/next/abbox-pre-swc-loader.cjs +44 -0
- package/src/next/webpack-instrumentation.cjs +104 -26
package/dist/browser.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** Browser / Viewer-safe entry (no Babel, Vite, or Next build tooling). */
|
|
2
|
-
export * from "./protocol.js";
|
|
3
|
-
export * from "./ir-match.js";
|
|
4
|
-
export * from "./instrument/constants.js";
|
|
5
2
|
export { createInspectorClient } from "./host/client-bridge.js";
|
|
6
3
|
export { createInspectorHost } from "./host/host-bridge.js";
|
|
7
4
|
export { validateIncomingMessage } from "./host/validate.js";
|
|
5
|
+
export * from "./instrument/constants.js";
|
|
6
|
+
export * from "./ir-match.js";
|
|
7
|
+
export * from "./protocol.js";
|
|
8
8
|
export { createInspectorRuntime } from "./runtime/inspector.js";
|
|
9
9
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,2EAA2E;
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/browser.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** Browser / Viewer-safe entry (no Babel, Vite, or Next build tooling). */
|
|
2
|
-
export * from "./protocol.js";
|
|
3
|
-
export * from "./ir-match.js";
|
|
4
|
-
export * from "./instrument/constants.js";
|
|
5
2
|
export { createInspectorClient } from "./host/client-bridge.js";
|
|
6
3
|
export { createInspectorHost } from "./host/host-bridge.js";
|
|
7
4
|
export { validateIncomingMessage } from "./host/validate.js";
|
|
5
|
+
export * from "./instrument/constants.js";
|
|
6
|
+
export * from "./ir-match.js";
|
|
7
|
+
export * from "./protocol.js";
|
|
8
8
|
export { createInspectorRuntime } from "./runtime/inspector.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ABBBOX_INSPECTOR_CHANNEL, PROTOCOL_VERSION, } from "../protocol.js";
|
|
2
|
-
import { withLocalhostAliases } from "./origin-aliases.js";
|
|
3
2
|
import { createInspectorRuntime } from "../runtime/inspector.js";
|
|
3
|
+
import { withLocalhostAliases } from "./origin-aliases.js";
|
|
4
4
|
import { validateIncomingMessage } from "./validate.js";
|
|
5
5
|
export function createInspectorClient(options) {
|
|
6
6
|
const win = options.window ?? globalThis.window;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAGzB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,YAAY,GAAG,OAAO,CACzB,mBAAmB,EACnB;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAC5B,CAAC,SAAS,CAAC,CAAC;AACb,KAAK,aAAa,GAAG,OAAO,CAC1B,mBAAmB,EACnB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAC7B,CAAC,SAAS,CAAC,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACtD,0EAA0E;IAC1E,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,qBAAqB,GAC9B,aAAa,
|
|
1
|
+
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAGzB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,YAAY,GAAG,OAAO,CACzB,mBAAmB,EACnB;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAC5B,CAAC,SAAS,CAAC,CAAC;AACb,KAAK,aAAa,GAAG,OAAO,CAC1B,mBAAmB,EACnB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAC7B,CAAC,SAAS,CAAC,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACtD,0EAA0E;IAC1E,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,qBAAqB,GAC9B,aAAa,CA4Df"}
|
package/dist/host/host-bridge.js
CHANGED
|
@@ -53,7 +53,11 @@ export function createInspectorHost(options, handlers) {
|
|
|
53
53
|
send({ ...base(), type: "APPLY_CHANGE_MARKERS", markers });
|
|
54
54
|
},
|
|
55
55
|
pingPreview: () => {
|
|
56
|
-
send({
|
|
56
|
+
send({
|
|
57
|
+
...base(),
|
|
58
|
+
type: "INSPECTOR_HOST_HELLO",
|
|
59
|
+
hostOrigin: options.hostOrigin,
|
|
60
|
+
});
|
|
57
61
|
},
|
|
58
62
|
};
|
|
59
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin-aliases.d.ts","sourceRoot":"","sources":["../../src/host/origin-aliases.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,wBAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"origin-aliases.d.ts","sourceRoot":"","sources":["../../src/host/origin-aliases.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWzE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs Abbox JSX instrumentation on TSX/JSX before Next's SWC pass.
|
|
3
|
+
* No-ops for paths outside configured app source directories.
|
|
4
|
+
*/
|
|
5
|
+
const babel = require("@babel/core");
|
|
6
|
+
const { shouldInstrumentResource } = require("./webpack-instrumentation.cjs");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @this {import('webpack').LoaderContext<unknown>}
|
|
10
|
+
*/
|
|
11
|
+
module.exports = function abboxPreSwcLoader(source) {
|
|
12
|
+
const callback = this.async();
|
|
13
|
+
const options = this.getOptions();
|
|
14
|
+
const rootDir = options.rootDir ?? this.rootContext;
|
|
15
|
+
const resourcePath = this.resourcePath;
|
|
16
|
+
|
|
17
|
+
if (!shouldInstrumentResource(resourcePath, rootDir)) {
|
|
18
|
+
callback(null, source);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
babel.transform(
|
|
23
|
+
source,
|
|
24
|
+
{
|
|
25
|
+
filename: resourcePath,
|
|
26
|
+
babelrc: false,
|
|
27
|
+
configFile: false,
|
|
28
|
+
parserOpts: { plugins: ["jsx", "typescript"] },
|
|
29
|
+
plugins: options.plugins,
|
|
30
|
+
sourceMaps: this.sourceMap,
|
|
31
|
+
},
|
|
32
|
+
(error, result) => {
|
|
33
|
+
if (error) {
|
|
34
|
+
callback(error);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!result?.code) {
|
|
38
|
+
callback(null, source);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
callback(null, result.code, result.map);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
};
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type WithAbboxInspectorOptions, withAbboxInspector, DEFAULT_VIEWER_FRAME_ANCESTORS, } from "./with-abbox-inspector.js";
|
|
2
1
|
export { type AbboxInspectionMode, getBakedInspectionMode, getBakedViewerOrigin, isAbboxInspectorBuildEnabled, isAbboxInspectorProductionRuntime, isAbboxInspectorRuntimeEnabled, } from "./preview-env.js";
|
|
2
|
+
export { DEFAULT_VIEWER_FRAME_ANCESTORS, type WithAbboxInspectorOptions, withAbboxInspector, } from "./with-abbox-inspector.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/next/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,4BAA4B,EAC5B,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,8BAA8B,EAC9B,KAAK,yBAAyB,EAC9B,kBAAkB,GACnB,MAAM,2BAA2B,CAAC"}
|
package/dist/next/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { withAbboxInspector, DEFAULT_VIEWER_FRAME_ANCESTORS, } from "./with-abbox-inspector.js";
|
|
2
1
|
export { getBakedInspectionMode, getBakedViewerOrigin, isAbboxInspectorBuildEnabled, isAbboxInspectorProductionRuntime, isAbboxInspectorRuntimeEnabled, } from "./preview-env.js";
|
|
2
|
+
export { DEFAULT_VIEWER_FRAME_ANCESTORS, withAbboxInspector, } from "./with-abbox-inspector.js";
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Apply Abbox JSX instrumentation via webpack + babel-loader on selected app sources.
|
|
3
3
|
* Does not use babel.config.js, so Next.js keeps SWC (and next/font) for excluded files
|
|
4
4
|
* such as app/layout.tsx.
|
|
5
|
+
*
|
|
6
|
+
* Next.js App Router client layers compile with SWC before the default Babel pass, so a
|
|
7
|
+
* top-level babel rule is not enough. We prepend an Abbox babel-loader step on the App
|
|
8
|
+
* Router browser/SSR layer rules (before SWC runs).
|
|
5
9
|
*/
|
|
6
10
|
const fs = require("node:fs");
|
|
7
11
|
const path = require("node:path");
|
|
@@ -9,6 +13,11 @@ const { createRequire } = require("node:module");
|
|
|
9
13
|
|
|
10
14
|
const LAYOUT_BASENAMES = new Set(["layout.tsx", "layout.jsx"]);
|
|
11
15
|
|
|
16
|
+
/** Layers where Next compiles client/app TSX through SWC (must instrument before SWC). */
|
|
17
|
+
const APP_CLIENT_ISSUER_LAYERS = new Set(["app-pages-browser", "ssr"]);
|
|
18
|
+
|
|
19
|
+
const ABBBOX_LOADER_IDENT = "abbox-source-instrumentation";
|
|
20
|
+
|
|
12
21
|
function resolveInspectorBabelPlugin(rootDir) {
|
|
13
22
|
const req = createRequire(path.join(rootDir, "package.json"));
|
|
14
23
|
try {
|
|
@@ -18,29 +27,93 @@ function resolveInspectorBabelPlugin(rootDir) {
|
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
function resolveBabelLoader(rootDir) {
|
|
22
|
-
const req = createRequire(path.join(rootDir, "package.json"));
|
|
23
|
-
return req.resolve("babel-loader");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function resolveNextBabelPreset(rootDir) {
|
|
27
|
-
const req = createRequire(path.join(rootDir, "package.json"));
|
|
28
|
-
return req.resolve("next/babel");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
30
|
function collectIncludeDirs(rootDir) {
|
|
32
|
-
return ["app", "components", "src", "pages"]
|
|
31
|
+
return ["app", "components", "src", "pages", "lib", "hooks", "features"]
|
|
33
32
|
.map((segment) => path.join(rootDir, segment))
|
|
34
33
|
.filter((dir) => fs.existsSync(dir));
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function isRootLayoutResource(resourcePath) {
|
|
38
|
-
const
|
|
37
|
+
const clean = resourcePath.split("?")[0];
|
|
38
|
+
const base = path.basename(clean);
|
|
39
39
|
if (!LAYOUT_BASENAMES.has(base)) return false;
|
|
40
|
-
const parent = path.basename(path.dirname(
|
|
40
|
+
const parent = path.basename(path.dirname(clean));
|
|
41
41
|
return parent === "app" || parent === "src/app";
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function shouldInstrumentResource(resourcePath, rootDir) {
|
|
45
|
+
const clean = resourcePath.split("?")[0];
|
|
46
|
+
const includeDirs = collectIncludeDirs(rootDir);
|
|
47
|
+
if (!/\.(tsx|jsx)$/.test(clean)) return false;
|
|
48
|
+
return (
|
|
49
|
+
includeDirs.some((dir) => clean.startsWith(dir)) &&
|
|
50
|
+
!isRootLayoutResource(clean)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loaderIdent(entry) {
|
|
55
|
+
if (!entry) return "";
|
|
56
|
+
if (typeof entry === "string") return entry;
|
|
57
|
+
return String(entry.loader ?? entry.ident ?? "");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolvePreSwcLoader() {
|
|
61
|
+
return path.join(__dirname, "abbox-pre-swc-loader.cjs");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createAbboxBabelLoaderUse(pluginPath, rootDir) {
|
|
65
|
+
return {
|
|
66
|
+
loader: resolvePreSwcLoader(),
|
|
67
|
+
ident: ABBBOX_LOADER_IDENT,
|
|
68
|
+
options: {
|
|
69
|
+
rootDir,
|
|
70
|
+
plugins: [[pluginPath, { rootDir }]],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ruleUsesSwc(rule) {
|
|
76
|
+
const uses = rule.use
|
|
77
|
+
? Array.isArray(rule.use)
|
|
78
|
+
? rule.use
|
|
79
|
+
: [rule.use]
|
|
80
|
+
: [];
|
|
81
|
+
return uses.some((u) => loaderIdent(u).includes("next-swc-loader"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ruleHasAbboxLoader(rule) {
|
|
85
|
+
const uses = rule.use
|
|
86
|
+
? Array.isArray(rule.use)
|
|
87
|
+
? rule.use
|
|
88
|
+
: [rule.use]
|
|
89
|
+
: [];
|
|
90
|
+
return uses.some((u) => loaderIdent(u) === ABBBOX_LOADER_IDENT);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {unknown[]} rules
|
|
95
|
+
* @param {object} abboxUse
|
|
96
|
+
*/
|
|
97
|
+
function patchAppRouterLayerRules(rules, abboxUse) {
|
|
98
|
+
if (!Array.isArray(rules)) return;
|
|
99
|
+
for (const rule of rules) {
|
|
100
|
+
if (!rule || typeof rule !== "object") continue;
|
|
101
|
+
if (Array.isArray(rule.oneOf)) {
|
|
102
|
+
patchAppRouterLayerRules(rule.oneOf, abboxUse);
|
|
103
|
+
}
|
|
104
|
+
const layer = rule.issuerLayer;
|
|
105
|
+
if (
|
|
106
|
+
typeof layer === "string" &&
|
|
107
|
+
APP_CLIENT_ISSUER_LAYERS.has(layer) &&
|
|
108
|
+
ruleUsesSwc(rule) &&
|
|
109
|
+
!ruleHasAbboxLoader(rule)
|
|
110
|
+
) {
|
|
111
|
+
const uses = Array.isArray(rule.use) ? rule.use : [rule.use];
|
|
112
|
+
rule.use = [...uses, abboxUse];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
44
117
|
/**
|
|
45
118
|
* @param {import('webpack').Configuration} webpackConfig
|
|
46
119
|
* @param {{ dir: string }} options
|
|
@@ -48,35 +121,40 @@ function isRootLayoutResource(resourcePath) {
|
|
|
48
121
|
function applyAbboxWebpackInstrumentation(webpackConfig, options) {
|
|
49
122
|
const rootDir = options.dir ?? process.cwd();
|
|
50
123
|
const pluginPath = resolveInspectorBabelPlugin(rootDir);
|
|
51
|
-
const babelLoader = resolveBabelLoader(rootDir);
|
|
52
|
-
const nextBabel = resolveNextBabelPreset(rootDir);
|
|
53
124
|
const includeDirs = collectIncludeDirs(rootDir);
|
|
54
125
|
|
|
55
126
|
if (includeDirs.length === 0) {
|
|
56
127
|
return webpackConfig;
|
|
57
128
|
}
|
|
58
129
|
|
|
130
|
+
const abboxUse = createAbboxBabelLoaderUse(pluginPath, rootDir);
|
|
131
|
+
|
|
132
|
+
webpackConfig.module = webpackConfig.module ?? { rules: [] };
|
|
133
|
+
webpackConfig.module.rules = webpackConfig.module.rules ?? [];
|
|
134
|
+
|
|
135
|
+
patchAppRouterLayerRules(webpackConfig.module.rules, abboxUse);
|
|
136
|
+
|
|
137
|
+
// Pages Router and non-layered TSX: top-level rule runs before Next's oneOf chains.
|
|
59
138
|
const rule = {
|
|
60
139
|
test: /\.(tsx|jsx)$/,
|
|
61
|
-
include: (resourcePath) =>
|
|
62
|
-
includeDirs.some((dir) => resourcePath.startsWith(dir)) &&
|
|
63
|
-
!isRootLayoutResource(resourcePath),
|
|
140
|
+
include: (resourcePath) => shouldInstrumentResource(resourcePath, rootDir),
|
|
64
141
|
exclude: [/node_modules/],
|
|
65
142
|
use: {
|
|
66
|
-
loader:
|
|
143
|
+
loader: resolvePreSwcLoader(),
|
|
67
144
|
options: {
|
|
68
|
-
|
|
69
|
-
configFile: false,
|
|
70
|
-
presets: [nextBabel],
|
|
145
|
+
rootDir,
|
|
71
146
|
plugins: [[pluginPath, { rootDir }]],
|
|
72
147
|
},
|
|
73
148
|
},
|
|
74
149
|
};
|
|
75
|
-
|
|
76
|
-
webpackConfig.module = webpackConfig.module ?? { rules: [] };
|
|
77
|
-
webpackConfig.module.rules = webpackConfig.module.rules ?? [];
|
|
78
150
|
webpackConfig.module.rules.unshift(rule);
|
|
151
|
+
|
|
79
152
|
return webpackConfig;
|
|
80
153
|
}
|
|
81
154
|
|
|
82
|
-
module.exports = {
|
|
155
|
+
module.exports = {
|
|
156
|
+
applyAbboxWebpackInstrumentation,
|
|
157
|
+
isRootLayoutResource,
|
|
158
|
+
shouldInstrumentResource,
|
|
159
|
+
APP_CLIENT_ISSUER_LAYERS,
|
|
160
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-abbox-inspector.d.ts","sourceRoot":"","sources":["../../src/next/with-abbox-inspector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"with-abbox-inspector.d.ts","sourceRoot":"","sources":["../../src/next/with-abbox-inspector.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CACR,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,eAAO,MAAM,8BAA8B,UAU1C,CAAC;AAcF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,yBAA8B,GACtC,CAAC,CAgFH"}
|
|
@@ -4,6 +4,10 @@ const require = createRequire(fileURLToPath(import.meta.url));
|
|
|
4
4
|
import { isAbboxInspectorBuildEnabled } from "./preview-env.js";
|
|
5
5
|
export const DEFAULT_VIEWER_FRAME_ANCESTORS = [
|
|
6
6
|
"https://viewer.abbox.com",
|
|
7
|
+
// The hosted product embeds the Viewer, so browsers require the product
|
|
8
|
+
// origin to be allowed as an ancestor as well as the immediate Viewer.
|
|
9
|
+
"https://www.abbox.com",
|
|
10
|
+
"https://abbox.com",
|
|
7
11
|
"http://localhost:5173",
|
|
8
12
|
"http://127.0.0.1:5173",
|
|
9
13
|
"http://localhost:4173",
|
package/dist/protocol.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type InspectorDisableMessage = InspectorMessageBase & {
|
|
|
19
19
|
/** Host asks the preview client to resend INSPECTOR_READY (e.g. after iframe load). */
|
|
20
20
|
export type InspectorHostHelloMessage = InspectorMessageBase & {
|
|
21
21
|
type: "INSPECTOR_HOST_HELLO";
|
|
22
|
+
/** Viewer origin that opened the preview (validated by the client in production). */
|
|
23
|
+
hostOrigin: string;
|
|
22
24
|
};
|
|
23
25
|
export type ElementRect = {
|
|
24
26
|
x: number;
|
|
@@ -42,6 +44,8 @@ export type ElementMetadata = {
|
|
|
42
44
|
role: string | null;
|
|
43
45
|
ariaLabel: string | null;
|
|
44
46
|
textPreview: string | null;
|
|
47
|
+
/** Native disabled state when the element supports it (e.g. button, input). */
|
|
48
|
+
disabled: boolean | null;
|
|
45
49
|
attributes: Record<string, string>;
|
|
46
50
|
};
|
|
47
51
|
export type RuntimeElementRef = {
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAE3C,eAAO,MAAM,wBAAwB,EAAG,iBAA0B,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,wBAAwB,CAAC;IACzC,OAAO,EAAE,OAAO,gBAAgB,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,GAAG;IACzD,IAAI,EAAE,iBAAiB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,GAAG;IAC1D,IAAI,EAAE,kBAAkB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,GAAG;IAC3D,IAAI,EAAE,mBAAmB,CAAC;CAC3B,CAAC;AAEF,uFAAuF;AACvF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,IAAI,EAAE,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { InspectorHostHelloMessage } from "../protocol.js";
|
|
2
|
+
export declare function parseValidatedHostHello(data: unknown, eventOrigin: string, allowedViewerOrigins: readonly string[]): InspectorHostHelloMessage | null;
|
|
3
|
+
//# sourceMappingURL=bootstrap-standby.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap-standby.d.ts","sourceRoot":"","sources":["../../src/runtime/bootstrap-standby.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAGhE,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,EACnB,oBAAoB,EAAE,SAAS,MAAM,EAAE,GACtC,yBAAyB,GAAG,IAAI,CAkBlC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { withLocalhostAliases } from "../host/origin-aliases.js";
|
|
2
|
+
import { getBakedViewerOrigin } from "../next/preview-env.js";
|
|
3
|
+
import { isInspectorMessage } from "../protocol.js";
|
|
4
|
+
export function parseValidatedHostHello(data, eventOrigin, allowedViewerOrigins) {
|
|
5
|
+
if (!allowedViewerOrigins.includes(eventOrigin))
|
|
6
|
+
return null;
|
|
7
|
+
if (!isInspectorMessage(data) || data.type !== "INSPECTOR_HOST_HELLO") {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const hello = data;
|
|
11
|
+
if (!hello.hostOrigin?.trim())
|
|
12
|
+
return null;
|
|
13
|
+
const bakedViewer = getBakedViewerOrigin();
|
|
14
|
+
const allowedHostOrigins = withLocalhostAliases(bakedViewer ? [bakedViewer] : allowedViewerOrigins);
|
|
15
|
+
const normalizedHost = hello.hostOrigin.replace(/\/$/, "");
|
|
16
|
+
const hostOk = allowedHostOrigins.some((o) => o.replace(/\/$/, "") === normalizedHost);
|
|
17
|
+
if (!hostOk)
|
|
18
|
+
return null;
|
|
19
|
+
return hello;
|
|
20
|
+
}
|
|
@@ -3,8 +3,11 @@ export type BootstrapOptions = {
|
|
|
3
3
|
allowedHostOrigins?: string[];
|
|
4
4
|
};
|
|
5
5
|
/**
|
|
6
|
-
* Entry for instrumented apps. Activates
|
|
7
|
-
* inspection mode and
|
|
6
|
+
* Entry for instrumented apps. Activates when the build baked an authorized
|
|
7
|
+
* inspection mode and either URL session params are present or (production only)
|
|
8
|
+
* a validated INSPECTOR_HOST_HELLO arrives from the Viewer.
|
|
8
9
|
*/
|
|
9
10
|
export declare function bootstrapInspectorPreview(options?: BootstrapOptions): void;
|
|
11
|
+
/** Test-only reset for vitest isolation. */
|
|
12
|
+
export declare function resetInspectorBootstrapForTests(): void;
|
|
10
13
|
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/runtime/bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/runtime/bootstrap.ts"],"names":[],"mappings":"AAuCA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AA8CF;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gBAAqB,GAC7B,IAAI,CAyCN;AAED,4CAA4C;AAC5C,wBAAgB,+BAA+B,IAAI,IAAI,CAMtD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { createInspectorClient, } from "../host/client-bridge.js";
|
|
1
2
|
import { withLocalhostAliases } from "../host/origin-aliases.js";
|
|
2
|
-
import { createInspectorClient } from "../host/client-bridge.js";
|
|
3
3
|
import { getBakedViewerOrigin, isAbboxInspectorProductionRuntime, isAbboxInspectorRuntimeEnabled, } from "../next/preview-env.js";
|
|
4
|
+
import { parseValidatedHostHello } from "./bootstrap-standby.js";
|
|
4
5
|
/** Viewer origins used when `abboxHostOrigin` is missing (preview mode only). */
|
|
5
6
|
const DEFAULT_VIEWER_ORIGINS = [
|
|
6
7
|
"https://viewer.abbox.com",
|
|
@@ -26,25 +27,67 @@ function isAllowedHostOrigin(hostOrigin, allowed) {
|
|
|
26
27
|
const normalized = normalizeOrigin(hostOrigin);
|
|
27
28
|
return allowed.some((o) => normalizeOrigin(o) === normalized);
|
|
28
29
|
}
|
|
30
|
+
let activeClient = null;
|
|
31
|
+
let standbyMessageListener = null;
|
|
32
|
+
function allowedViewerOriginsForStandby() {
|
|
33
|
+
const bakedViewer = getBakedViewerOrigin();
|
|
34
|
+
return withLocalhostAliases(bakedViewer ? [bakedViewer] : DEFAULT_VIEWER_ORIGINS);
|
|
35
|
+
}
|
|
36
|
+
function removeStandbyListener(win) {
|
|
37
|
+
if (!standbyMessageListener)
|
|
38
|
+
return;
|
|
39
|
+
win.removeEventListener("message", standbyMessageListener);
|
|
40
|
+
standbyMessageListener = null;
|
|
41
|
+
}
|
|
42
|
+
function startInspectorClient(sessionId, allowedHostOrigins) {
|
|
43
|
+
if (activeClient || typeof window === "undefined")
|
|
44
|
+
return;
|
|
45
|
+
removeStandbyListener(window);
|
|
46
|
+
activeClient = createInspectorClient({
|
|
47
|
+
sessionId,
|
|
48
|
+
allowedHostOrigins,
|
|
49
|
+
});
|
|
50
|
+
activeClient.start();
|
|
51
|
+
}
|
|
52
|
+
function installStandbySessionListener(win) {
|
|
53
|
+
if (standbyMessageListener)
|
|
54
|
+
return;
|
|
55
|
+
const allowedViewerOrigins = allowedViewerOriginsForStandby();
|
|
56
|
+
standbyMessageListener = (event) => {
|
|
57
|
+
const hello = parseValidatedHostHello(event.data, event.origin, allowedViewerOrigins);
|
|
58
|
+
if (!hello)
|
|
59
|
+
return;
|
|
60
|
+
startInspectorClient(hello.sessionId, withLocalhostAliases([hello.hostOrigin]));
|
|
61
|
+
};
|
|
62
|
+
win.addEventListener("message", standbyMessageListener);
|
|
63
|
+
}
|
|
29
64
|
/**
|
|
30
|
-
* Entry for instrumented apps. Activates
|
|
31
|
-
* inspection mode and
|
|
65
|
+
* Entry for instrumented apps. Activates when the build baked an authorized
|
|
66
|
+
* inspection mode and either URL session params are present or (production only)
|
|
67
|
+
* a validated INSPECTOR_HOST_HELLO arrives from the Viewer.
|
|
32
68
|
*/
|
|
33
69
|
export function bootstrapInspectorPreview(options = {}) {
|
|
34
70
|
if (typeof window === "undefined")
|
|
35
71
|
return;
|
|
36
72
|
if (!isAbboxInspectorRuntimeEnabled())
|
|
37
73
|
return;
|
|
38
|
-
const
|
|
74
|
+
const win = window;
|
|
75
|
+
const params = new URLSearchParams(win.location.search);
|
|
39
76
|
const sessionFromUrl = params.get("abboxSession");
|
|
40
|
-
|
|
77
|
+
const hostParam = params.get("abboxHostOrigin");
|
|
78
|
+
if (!sessionFromUrl) {
|
|
79
|
+
if (isAbboxInspectorProductionRuntime()) {
|
|
80
|
+
installStandbySessionListener(win);
|
|
81
|
+
}
|
|
41
82
|
return;
|
|
83
|
+
}
|
|
42
84
|
const sessionId = options.sessionId ?? sessionFromUrl;
|
|
43
|
-
const hostParam = params.get("abboxHostOrigin");
|
|
44
85
|
let allowedHostOrigins;
|
|
45
86
|
if (isAbboxInspectorProductionRuntime()) {
|
|
46
|
-
if (!hostParam)
|
|
87
|
+
if (!hostParam) {
|
|
88
|
+
installStandbySessionListener(win);
|
|
47
89
|
return;
|
|
90
|
+
}
|
|
48
91
|
const bakedViewer = getBakedViewerOrigin();
|
|
49
92
|
const allowed = withLocalhostAliases(options.allowedHostOrigins ??
|
|
50
93
|
(bakedViewer ? [bakedViewer] : DEFAULT_VIEWER_ORIGINS));
|
|
@@ -58,9 +101,13 @@ export function bootstrapInspectorPreview(options = {}) {
|
|
|
58
101
|
? [hostParam]
|
|
59
102
|
: [...viewerOriginsFromReferrer(), ...DEFAULT_VIEWER_ORIGINS]));
|
|
60
103
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
startInspectorClient(sessionId, allowedHostOrigins);
|
|
105
|
+
}
|
|
106
|
+
/** Test-only reset for vitest isolation. */
|
|
107
|
+
export function resetInspectorBootstrapForTests() {
|
|
108
|
+
if (typeof window !== "undefined") {
|
|
109
|
+
removeStandbyListener(window);
|
|
110
|
+
}
|
|
111
|
+
activeClient?.stop();
|
|
112
|
+
activeClient = null;
|
|
66
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../src/runtime/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAgBxE,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,gBAAgB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,
|
|
1
|
+
{"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../src/runtime/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAgBxE,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,gBAAgB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,CAsIlB"}
|
|
@@ -12,7 +12,7 @@ export function createInspectorRuntime(options) {
|
|
|
12
12
|
let pendingTarget = null;
|
|
13
13
|
const teardownListeners = () => {
|
|
14
14
|
win.removeEventListener("mousemove", onMove, true);
|
|
15
|
-
win.removeEventListener("
|
|
15
|
+
win.removeEventListener("pointerdown", onPointerDownCapture, true);
|
|
16
16
|
win.removeEventListener("scroll", onScrollOrResize, true);
|
|
17
17
|
win.removeEventListener("resize", onScrollOrResize);
|
|
18
18
|
};
|
|
@@ -33,9 +33,12 @@ export function createInspectorRuntime(options) {
|
|
|
33
33
|
pendingTarget = null;
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
/** Disabled controls do not dispatch `click`; use pointer + hit-testing. */
|
|
37
|
+
const onPointerDownCapture = (event) => {
|
|
37
38
|
if (!enabled)
|
|
38
39
|
return;
|
|
40
|
+
if (event.button !== 0)
|
|
41
|
+
return;
|
|
39
42
|
event.preventDefault();
|
|
40
43
|
event.stopPropagation();
|
|
41
44
|
const target = deepestElementAt(doc, event.clientX, event.clientY, event.target);
|
|
@@ -86,7 +89,7 @@ export function createInspectorRuntime(options) {
|
|
|
86
89
|
return;
|
|
87
90
|
enabled = true;
|
|
88
91
|
win.addEventListener("mousemove", onMove, true);
|
|
89
|
-
win.addEventListener("
|
|
92
|
+
win.addEventListener("pointerdown", onPointerDownCapture, true);
|
|
90
93
|
win.addEventListener("scroll", onScrollOrResize, true);
|
|
91
94
|
win.addEventListener("resize", onScrollOrResize);
|
|
92
95
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/runtime/metadata.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAYxB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAarE;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,cAAc,GAAG,IAAI,GAC5B,mBAAmB,CAGrB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/runtime/metadata.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAYxB,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAarE;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,cAAc,GAAG,IAAI,GAC5B,mBAAmB,CAGrB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,GAAG,eAAe,CAiC5D;AA+BD,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAGzE"}
|
package/dist/runtime/metadata.js
CHANGED
|
@@ -58,9 +58,24 @@ export function extractMetadata(el) {
|
|
|
58
58
|
role,
|
|
59
59
|
ariaLabel,
|
|
60
60
|
textPreview,
|
|
61
|
+
disabled: readDisabledState(el),
|
|
61
62
|
attributes,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
65
|
+
function readDisabledState(el) {
|
|
66
|
+
if (el instanceof HTMLButtonElement ||
|
|
67
|
+
el instanceof HTMLInputElement ||
|
|
68
|
+
el instanceof HTMLSelectElement ||
|
|
69
|
+
el instanceof HTMLOptionElement ||
|
|
70
|
+
el instanceof HTMLTextAreaElement ||
|
|
71
|
+
el instanceof HTMLFieldSetElement) {
|
|
72
|
+
return el.disabled;
|
|
73
|
+
}
|
|
74
|
+
if (el instanceof HTMLElement && el.hasAttribute("disabled")) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
64
79
|
function textPreviewFor(el) {
|
|
65
80
|
const own = Array.from(el.childNodes)
|
|
66
81
|
.filter((n) => n.nodeType === Node.TEXT_NODE)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChangeMarker } from "../protocol.js";
|
|
2
|
+
export type DomMatchConfidence = "exact" | "probable" | "ambiguous" | "unresolved";
|
|
3
|
+
export type ChangeMarkerResolution = {
|
|
4
|
+
changeId: string;
|
|
5
|
+
confidence: DomMatchConfidence;
|
|
6
|
+
candidateCount: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function resolveChangeMarker(marker: ChangeMarker, doc: Document, pathname: string, refIdToElement: Map<string, Element>): {
|
|
9
|
+
element: Element | null;
|
|
10
|
+
resolution: ChangeMarkerResolution;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=resolve-change-marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-change-marker.d.ts","sourceRoot":"","sources":["../../src/runtime/resolve-change-marker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC;AAEnF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAiCF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC;IAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,UAAU,EAAE,sBAAsB,CAAA;CAAE,CAqEjE"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function routeMatches(markerRoute, pathname) {
|
|
2
|
+
if (!markerRoute?.trim())
|
|
3
|
+
return true;
|
|
4
|
+
const route = markerRoute.trim();
|
|
5
|
+
if (route === pathname)
|
|
6
|
+
return true;
|
|
7
|
+
if (route.endsWith("*") && pathname.startsWith(route.slice(0, -1)))
|
|
8
|
+
return true;
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
function collectSourcePrefixMatches(doc, prefix) {
|
|
12
|
+
const matches = [];
|
|
13
|
+
const nodes = doc.querySelectorAll("[data-abbox-src]");
|
|
14
|
+
for (const node of nodes) {
|
|
15
|
+
const attr = node.getAttribute("data-abbox-src");
|
|
16
|
+
if (attr?.startsWith(prefix)) {
|
|
17
|
+
matches.push(node);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return matches;
|
|
21
|
+
}
|
|
22
|
+
function collectElementIdMatches(doc, elementId) {
|
|
23
|
+
const escaped = elementId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
24
|
+
const byId = doc.querySelectorAll(`[id="${escaped}"]`);
|
|
25
|
+
if (byId.length > 0)
|
|
26
|
+
return [...byId];
|
|
27
|
+
const byData = doc.querySelectorAll(`[data-abbox-element-id="${escaped}"]`);
|
|
28
|
+
return [...byData];
|
|
29
|
+
}
|
|
30
|
+
export function resolveChangeMarker(marker, doc, pathname, refIdToElement) {
|
|
31
|
+
const base = {
|
|
32
|
+
changeId: marker.elementRefId,
|
|
33
|
+
confidence: "unresolved",
|
|
34
|
+
candidateCount: 0,
|
|
35
|
+
};
|
|
36
|
+
if (!routeMatches(marker.route, pathname)) {
|
|
37
|
+
return { element: null, resolution: base };
|
|
38
|
+
}
|
|
39
|
+
const byRef = refIdToElement.get(marker.elementRefId);
|
|
40
|
+
if (byRef) {
|
|
41
|
+
return {
|
|
42
|
+
element: byRef,
|
|
43
|
+
resolution: { ...base, confidence: "exact", candidateCount: 1 },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
let candidates = [];
|
|
47
|
+
if (marker.compilerElementId?.trim()) {
|
|
48
|
+
candidates = collectElementIdMatches(doc, marker.compilerElementId.trim());
|
|
49
|
+
if (candidates.length === 1) {
|
|
50
|
+
return {
|
|
51
|
+
element: candidates[0],
|
|
52
|
+
resolution: { ...base, confidence: "exact", candidateCount: 1 },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (candidates.length > 1) {
|
|
56
|
+
return {
|
|
57
|
+
element: null,
|
|
58
|
+
resolution: { ...base, confidence: "ambiguous", candidateCount: candidates.length },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const prefix = marker.sourceKeyPrefix?.trim();
|
|
63
|
+
if (prefix) {
|
|
64
|
+
candidates = collectSourcePrefixMatches(doc, prefix);
|
|
65
|
+
if (marker.interactionId) {
|
|
66
|
+
// Narrow by label hint when multiple nodes share a source line.
|
|
67
|
+
const label = marker.label.toLowerCase();
|
|
68
|
+
const narrowed = candidates.filter((el) => {
|
|
69
|
+
const text = (el.textContent ?? "").toLowerCase();
|
|
70
|
+
return label.length > 0 && text.includes(label.slice(0, 24));
|
|
71
|
+
});
|
|
72
|
+
if (narrowed.length === 1) {
|
|
73
|
+
return {
|
|
74
|
+
element: narrowed[0],
|
|
75
|
+
resolution: { ...base, confidence: "probable", candidateCount: 1 },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (candidates.length === 1) {
|
|
80
|
+
return {
|
|
81
|
+
element: candidates[0],
|
|
82
|
+
resolution: { ...base, confidence: "probable", candidateCount: 1 },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (candidates.length > 1) {
|
|
86
|
+
return {
|
|
87
|
+
element: null,
|
|
88
|
+
resolution: { ...base, confidence: "ambiguous", candidateCount: candidates.length },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { element: null, resolution: base };
|
|
93
|
+
}
|
|
@@ -28,7 +28,8 @@ npm run dev:abbox
|
|
|
28
28
|
## Vercel
|
|
29
29
|
|
|
30
30
|
- **Preview**: `VERCEL_ENV=preview` → `next build --webpack` + inspector headers/runtime.
|
|
31
|
-
- **Production**:
|
|
31
|
+
- **Production (inspectable)**: use `@abbox/cli` production mode (`inspectionMode: "production"` in `.abbox/install.json`) so the build script sets `ABBBOX_INSPECTION_MODE=production` and runs `next build --webpack`. App Router client layers are instrumented **before** SWC via a dedicated babel-loader step.
|
|
32
|
+
- **Production (default)**: plain `next build` without Abbox env → no instrumentation (by design).
|
|
32
33
|
|
|
33
34
|
## Trust
|
|
34
35
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abbox/inspector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -52,11 +52,13 @@
|
|
|
52
52
|
"dist",
|
|
53
53
|
"src/next/babel-config.cjs",
|
|
54
54
|
"src/next/webpack-instrumentation.cjs",
|
|
55
|
+
"src/next/abbox-pre-swc-loader.cjs",
|
|
55
56
|
"docs",
|
|
56
57
|
"README.md"
|
|
57
58
|
],
|
|
58
59
|
"scripts": {
|
|
59
|
-
"
|
|
60
|
+
"prepare": "npm run build",
|
|
61
|
+
"build": "tsc -p tsconfig.build.json && node -e \"const fs=require('fs');const p='dist/next';fs.mkdirSync(p,{recursive:true});fs.copyFileSync('src/next/webpack-instrumentation.cjs',p+'/webpack-instrumentation.cjs');fs.copyFileSync('src/next/abbox-pre-swc-loader.cjs',p+'/abbox-pre-swc-loader.cjs');fs.copyFileSync('src/next/babel-config.cjs',p+'/babel-config.cjs');\"",
|
|
60
62
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
61
63
|
"test": "npm run build && vitest run",
|
|
62
64
|
"lint": "biome ci .",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs Abbox JSX instrumentation on TSX/JSX before Next's SWC pass.
|
|
3
|
+
* No-ops for paths outside configured app source directories.
|
|
4
|
+
*/
|
|
5
|
+
const babel = require("@babel/core");
|
|
6
|
+
const { shouldInstrumentResource } = require("./webpack-instrumentation.cjs");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @this {import('webpack').LoaderContext<unknown>}
|
|
10
|
+
*/
|
|
11
|
+
module.exports = function abboxPreSwcLoader(source) {
|
|
12
|
+
const callback = this.async();
|
|
13
|
+
const options = this.getOptions();
|
|
14
|
+
const rootDir = options.rootDir ?? this.rootContext;
|
|
15
|
+
const resourcePath = this.resourcePath;
|
|
16
|
+
|
|
17
|
+
if (!shouldInstrumentResource(resourcePath, rootDir)) {
|
|
18
|
+
callback(null, source);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
babel.transform(
|
|
23
|
+
source,
|
|
24
|
+
{
|
|
25
|
+
filename: resourcePath,
|
|
26
|
+
babelrc: false,
|
|
27
|
+
configFile: false,
|
|
28
|
+
parserOpts: { plugins: ["jsx", "typescript"] },
|
|
29
|
+
plugins: options.plugins,
|
|
30
|
+
sourceMaps: this.sourceMap,
|
|
31
|
+
},
|
|
32
|
+
(error, result) => {
|
|
33
|
+
if (error) {
|
|
34
|
+
callback(error);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!result?.code) {
|
|
38
|
+
callback(null, source);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
callback(null, result.code, result.map);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Apply Abbox JSX instrumentation via webpack + babel-loader on selected app sources.
|
|
3
3
|
* Does not use babel.config.js, so Next.js keeps SWC (and next/font) for excluded files
|
|
4
4
|
* such as app/layout.tsx.
|
|
5
|
+
*
|
|
6
|
+
* Next.js App Router client layers compile with SWC before the default Babel pass, so a
|
|
7
|
+
* top-level babel rule is not enough. We prepend an Abbox babel-loader step on the App
|
|
8
|
+
* Router browser/SSR layer rules (before SWC runs).
|
|
5
9
|
*/
|
|
6
10
|
const fs = require("node:fs");
|
|
7
11
|
const path = require("node:path");
|
|
@@ -9,6 +13,11 @@ const { createRequire } = require("node:module");
|
|
|
9
13
|
|
|
10
14
|
const LAYOUT_BASENAMES = new Set(["layout.tsx", "layout.jsx"]);
|
|
11
15
|
|
|
16
|
+
/** Layers where Next compiles client/app TSX through SWC (must instrument before SWC). */
|
|
17
|
+
const APP_CLIENT_ISSUER_LAYERS = new Set(["app-pages-browser", "ssr"]);
|
|
18
|
+
|
|
19
|
+
const ABBBOX_LOADER_IDENT = "abbox-source-instrumentation";
|
|
20
|
+
|
|
12
21
|
function resolveInspectorBabelPlugin(rootDir) {
|
|
13
22
|
const req = createRequire(path.join(rootDir, "package.json"));
|
|
14
23
|
try {
|
|
@@ -18,29 +27,93 @@ function resolveInspectorBabelPlugin(rootDir) {
|
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
function resolveBabelLoader(rootDir) {
|
|
22
|
-
const req = createRequire(path.join(rootDir, "package.json"));
|
|
23
|
-
return req.resolve("babel-loader");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function resolveNextBabelPreset(rootDir) {
|
|
27
|
-
const req = createRequire(path.join(rootDir, "package.json"));
|
|
28
|
-
return req.resolve("next/babel");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
30
|
function collectIncludeDirs(rootDir) {
|
|
32
|
-
return ["app", "components", "src", "pages"]
|
|
31
|
+
return ["app", "components", "src", "pages", "lib", "hooks", "features"]
|
|
33
32
|
.map((segment) => path.join(rootDir, segment))
|
|
34
33
|
.filter((dir) => fs.existsSync(dir));
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function isRootLayoutResource(resourcePath) {
|
|
38
|
-
const
|
|
37
|
+
const clean = resourcePath.split("?")[0];
|
|
38
|
+
const base = path.basename(clean);
|
|
39
39
|
if (!LAYOUT_BASENAMES.has(base)) return false;
|
|
40
|
-
const parent = path.basename(path.dirname(
|
|
40
|
+
const parent = path.basename(path.dirname(clean));
|
|
41
41
|
return parent === "app" || parent === "src/app";
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function shouldInstrumentResource(resourcePath, rootDir) {
|
|
45
|
+
const clean = resourcePath.split("?")[0];
|
|
46
|
+
const includeDirs = collectIncludeDirs(rootDir);
|
|
47
|
+
if (!/\.(tsx|jsx)$/.test(clean)) return false;
|
|
48
|
+
return (
|
|
49
|
+
includeDirs.some((dir) => clean.startsWith(dir)) &&
|
|
50
|
+
!isRootLayoutResource(clean)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loaderIdent(entry) {
|
|
55
|
+
if (!entry) return "";
|
|
56
|
+
if (typeof entry === "string") return entry;
|
|
57
|
+
return String(entry.loader ?? entry.ident ?? "");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolvePreSwcLoader() {
|
|
61
|
+
return path.join(__dirname, "abbox-pre-swc-loader.cjs");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createAbboxBabelLoaderUse(pluginPath, rootDir) {
|
|
65
|
+
return {
|
|
66
|
+
loader: resolvePreSwcLoader(),
|
|
67
|
+
ident: ABBBOX_LOADER_IDENT,
|
|
68
|
+
options: {
|
|
69
|
+
rootDir,
|
|
70
|
+
plugins: [[pluginPath, { rootDir }]],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ruleUsesSwc(rule) {
|
|
76
|
+
const uses = rule.use
|
|
77
|
+
? Array.isArray(rule.use)
|
|
78
|
+
? rule.use
|
|
79
|
+
: [rule.use]
|
|
80
|
+
: [];
|
|
81
|
+
return uses.some((u) => loaderIdent(u).includes("next-swc-loader"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function ruleHasAbboxLoader(rule) {
|
|
85
|
+
const uses = rule.use
|
|
86
|
+
? Array.isArray(rule.use)
|
|
87
|
+
? rule.use
|
|
88
|
+
: [rule.use]
|
|
89
|
+
: [];
|
|
90
|
+
return uses.some((u) => loaderIdent(u) === ABBBOX_LOADER_IDENT);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {unknown[]} rules
|
|
95
|
+
* @param {object} abboxUse
|
|
96
|
+
*/
|
|
97
|
+
function patchAppRouterLayerRules(rules, abboxUse) {
|
|
98
|
+
if (!Array.isArray(rules)) return;
|
|
99
|
+
for (const rule of rules) {
|
|
100
|
+
if (!rule || typeof rule !== "object") continue;
|
|
101
|
+
if (Array.isArray(rule.oneOf)) {
|
|
102
|
+
patchAppRouterLayerRules(rule.oneOf, abboxUse);
|
|
103
|
+
}
|
|
104
|
+
const layer = rule.issuerLayer;
|
|
105
|
+
if (
|
|
106
|
+
typeof layer === "string" &&
|
|
107
|
+
APP_CLIENT_ISSUER_LAYERS.has(layer) &&
|
|
108
|
+
ruleUsesSwc(rule) &&
|
|
109
|
+
!ruleHasAbboxLoader(rule)
|
|
110
|
+
) {
|
|
111
|
+
const uses = Array.isArray(rule.use) ? rule.use : [rule.use];
|
|
112
|
+
rule.use = [...uses, abboxUse];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
44
117
|
/**
|
|
45
118
|
* @param {import('webpack').Configuration} webpackConfig
|
|
46
119
|
* @param {{ dir: string }} options
|
|
@@ -48,35 +121,40 @@ function isRootLayoutResource(resourcePath) {
|
|
|
48
121
|
function applyAbboxWebpackInstrumentation(webpackConfig, options) {
|
|
49
122
|
const rootDir = options.dir ?? process.cwd();
|
|
50
123
|
const pluginPath = resolveInspectorBabelPlugin(rootDir);
|
|
51
|
-
const babelLoader = resolveBabelLoader(rootDir);
|
|
52
|
-
const nextBabel = resolveNextBabelPreset(rootDir);
|
|
53
124
|
const includeDirs = collectIncludeDirs(rootDir);
|
|
54
125
|
|
|
55
126
|
if (includeDirs.length === 0) {
|
|
56
127
|
return webpackConfig;
|
|
57
128
|
}
|
|
58
129
|
|
|
130
|
+
const abboxUse = createAbboxBabelLoaderUse(pluginPath, rootDir);
|
|
131
|
+
|
|
132
|
+
webpackConfig.module = webpackConfig.module ?? { rules: [] };
|
|
133
|
+
webpackConfig.module.rules = webpackConfig.module.rules ?? [];
|
|
134
|
+
|
|
135
|
+
patchAppRouterLayerRules(webpackConfig.module.rules, abboxUse);
|
|
136
|
+
|
|
137
|
+
// Pages Router and non-layered TSX: top-level rule runs before Next's oneOf chains.
|
|
59
138
|
const rule = {
|
|
60
139
|
test: /\.(tsx|jsx)$/,
|
|
61
|
-
include: (resourcePath) =>
|
|
62
|
-
includeDirs.some((dir) => resourcePath.startsWith(dir)) &&
|
|
63
|
-
!isRootLayoutResource(resourcePath),
|
|
140
|
+
include: (resourcePath) => shouldInstrumentResource(resourcePath, rootDir),
|
|
64
141
|
exclude: [/node_modules/],
|
|
65
142
|
use: {
|
|
66
|
-
loader:
|
|
143
|
+
loader: resolvePreSwcLoader(),
|
|
67
144
|
options: {
|
|
68
|
-
|
|
69
|
-
configFile: false,
|
|
70
|
-
presets: [nextBabel],
|
|
145
|
+
rootDir,
|
|
71
146
|
plugins: [[pluginPath, { rootDir }]],
|
|
72
147
|
},
|
|
73
148
|
},
|
|
74
149
|
};
|
|
75
|
-
|
|
76
|
-
webpackConfig.module = webpackConfig.module ?? { rules: [] };
|
|
77
|
-
webpackConfig.module.rules = webpackConfig.module.rules ?? [];
|
|
78
150
|
webpackConfig.module.rules.unshift(rule);
|
|
151
|
+
|
|
79
152
|
return webpackConfig;
|
|
80
153
|
}
|
|
81
154
|
|
|
82
|
-
module.exports = {
|
|
155
|
+
module.exports = {
|
|
156
|
+
applyAbboxWebpackInstrumentation,
|
|
157
|
+
isRootLayoutResource,
|
|
158
|
+
shouldInstrumentResource,
|
|
159
|
+
APP_CLIENT_ISSUER_LAYERS,
|
|
160
|
+
};
|