sitepong 0.2.7 → 0.2.8
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/entries/rn.js
CHANGED
|
@@ -9,12 +9,15 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
9
9
|
|
|
10
10
|
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
11
11
|
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
14
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
13
15
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
14
16
|
}) : x)(function(x) {
|
|
15
17
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
16
18
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
17
19
|
});
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
18
21
|
|
|
19
22
|
// src/platforms/rn/environment.ts
|
|
20
23
|
function createRNEnvironment(options = {}) {
|
|
@@ -42,8 +45,8 @@ function createRNEnvironment(options = {}) {
|
|
|
42
45
|
return `SitePong-RN/${[osPart, devicePart].filter(Boolean).join("; ")}`;
|
|
43
46
|
},
|
|
44
47
|
onUncaughtError(handler) {
|
|
45
|
-
const
|
|
46
|
-
const ErrorUtils2 =
|
|
48
|
+
const g2 = globalThis;
|
|
49
|
+
const ErrorUtils2 = g2.ErrorUtils;
|
|
47
50
|
if (!ErrorUtils2 || !ErrorUtils2.setGlobalHandler || !ErrorUtils2.getGlobalHandler) {
|
|
48
51
|
return () => {
|
|
49
52
|
};
|
|
@@ -95,10 +98,10 @@ function getEnvironment() {
|
|
|
95
98
|
}
|
|
96
99
|
function detectPlatform() {
|
|
97
100
|
if (currentEnv) return currentEnv.platform;
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
101
|
+
const g2 = globalThis;
|
|
102
|
+
if (g2.navigator && g2.navigator.product === "ReactNative") return "react-native";
|
|
103
|
+
if (g2.process && g2.process.versions && g2.process.versions.node) return "node";
|
|
104
|
+
if (g2.window && g2.document) return "browser";
|
|
102
105
|
return "unknown";
|
|
103
106
|
}
|
|
104
107
|
function isReactNative() {
|
|
@@ -2138,98 +2141,98 @@ function extractIdentityMetadata(url) {
|
|
|
2138
2141
|
// src/index.ts
|
|
2139
2142
|
function installFallbackEnvironment() {
|
|
2140
2143
|
if (getEnvironment()) return;
|
|
2141
|
-
const
|
|
2142
|
-
const hasBrowser = typeof
|
|
2143
|
-
const hasNode = !hasBrowser && !!
|
|
2144
|
+
const g2 = globalThis;
|
|
2145
|
+
const hasBrowser = typeof g2.window !== "undefined" && typeof g2.document !== "undefined";
|
|
2146
|
+
const hasNode = !hasBrowser && !!g2.process?.versions?.node;
|
|
2144
2147
|
const platform2 = hasBrowser ? "browser" : "node";
|
|
2145
2148
|
const env2 = {
|
|
2146
2149
|
platform: platform2,
|
|
2147
2150
|
storage: null,
|
|
2148
2151
|
getDeviceInfo() {
|
|
2149
2152
|
const info = { platform: platform2 };
|
|
2150
|
-
if (
|
|
2151
|
-
if (hasBrowser &&
|
|
2152
|
-
info.screenWidth =
|
|
2153
|
-
info.screenHeight =
|
|
2153
|
+
if (g2.navigator?.userAgent) info.userAgent = g2.navigator.userAgent;
|
|
2154
|
+
if (hasBrowser && g2.window?.screen) {
|
|
2155
|
+
info.screenWidth = g2.window.screen.width;
|
|
2156
|
+
info.screenHeight = g2.window.screen.height;
|
|
2154
2157
|
}
|
|
2155
|
-
if (hasNode &&
|
|
2156
|
-
info.osName =
|
|
2157
|
-
info.osVersion =
|
|
2158
|
+
if (hasNode && g2.process) {
|
|
2159
|
+
info.osName = g2.process.platform;
|
|
2160
|
+
info.osVersion = g2.process.versions?.node;
|
|
2158
2161
|
}
|
|
2159
2162
|
return info;
|
|
2160
2163
|
},
|
|
2161
2164
|
getCurrentUrl() {
|
|
2162
|
-
return
|
|
2165
|
+
return g2.window?.location?.href ?? void 0;
|
|
2163
2166
|
},
|
|
2164
2167
|
getReferrer() {
|
|
2165
|
-
return
|
|
2168
|
+
return g2.document?.referrer || void 0;
|
|
2166
2169
|
},
|
|
2167
2170
|
getUserAgent() {
|
|
2168
|
-
if (
|
|
2169
|
-
if (hasNode &&
|
|
2171
|
+
if (g2.navigator?.userAgent) return g2.navigator.userAgent;
|
|
2172
|
+
if (hasNode && g2.process) return `SitePong-Node/${g2.process.versions?.node} (${g2.process.platform})`;
|
|
2170
2173
|
return void 0;
|
|
2171
2174
|
},
|
|
2172
2175
|
onUncaughtError(handler) {
|
|
2173
|
-
if (hasBrowser &&
|
|
2176
|
+
if (hasBrowser && g2.window?.addEventListener) {
|
|
2174
2177
|
const listener = (event) => handler(event.error || event.message);
|
|
2175
|
-
|
|
2176
|
-
return () =>
|
|
2178
|
+
g2.window.addEventListener("error", listener);
|
|
2179
|
+
return () => g2.window?.removeEventListener?.("error", listener);
|
|
2177
2180
|
}
|
|
2178
|
-
if (hasNode &&
|
|
2181
|
+
if (hasNode && g2.process?.on && g2.process.off) {
|
|
2179
2182
|
const listener = (err) => handler(err);
|
|
2180
|
-
|
|
2181
|
-
return () =>
|
|
2183
|
+
g2.process.on("uncaughtException", listener);
|
|
2184
|
+
return () => g2.process?.off?.("uncaughtException", listener);
|
|
2182
2185
|
}
|
|
2183
2186
|
return () => {
|
|
2184
2187
|
};
|
|
2185
2188
|
},
|
|
2186
2189
|
onUnhandledRejection(handler) {
|
|
2187
|
-
if (hasBrowser &&
|
|
2190
|
+
if (hasBrowser && g2.window?.addEventListener) {
|
|
2188
2191
|
const listener = (event) => handler(event.reason);
|
|
2189
|
-
|
|
2190
|
-
return () =>
|
|
2192
|
+
g2.window.addEventListener("unhandledrejection", listener);
|
|
2193
|
+
return () => g2.window?.removeEventListener?.("unhandledrejection", listener);
|
|
2191
2194
|
}
|
|
2192
|
-
if (hasNode &&
|
|
2195
|
+
if (hasNode && g2.process?.on && g2.process.off) {
|
|
2193
2196
|
const listener = (reason) => handler(reason);
|
|
2194
|
-
|
|
2195
|
-
return () =>
|
|
2197
|
+
g2.process.on("unhandledRejection", listener);
|
|
2198
|
+
return () => g2.process?.off?.("unhandledRejection", listener);
|
|
2196
2199
|
}
|
|
2197
2200
|
return () => {
|
|
2198
2201
|
};
|
|
2199
2202
|
},
|
|
2200
2203
|
onBeforeTerminate(handler) {
|
|
2201
|
-
if (hasBrowser &&
|
|
2202
|
-
|
|
2203
|
-
return () =>
|
|
2204
|
+
if (hasBrowser && g2.window?.addEventListener) {
|
|
2205
|
+
g2.window.addEventListener("beforeunload", handler);
|
|
2206
|
+
return () => g2.window?.removeEventListener?.("beforeunload", handler);
|
|
2204
2207
|
}
|
|
2205
|
-
if (hasNode &&
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2208
|
+
if (hasNode && g2.process?.on && g2.process.off) {
|
|
2209
|
+
g2.process.on("beforeExit", handler);
|
|
2210
|
+
g2.process.on("SIGINT", handler);
|
|
2211
|
+
g2.process.on("SIGTERM", handler);
|
|
2209
2212
|
return () => {
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
+
g2.process?.off?.("beforeExit", handler);
|
|
2214
|
+
g2.process?.off?.("SIGINT", handler);
|
|
2215
|
+
g2.process?.off?.("SIGTERM", handler);
|
|
2213
2216
|
};
|
|
2214
2217
|
}
|
|
2215
2218
|
return () => {
|
|
2216
2219
|
};
|
|
2217
2220
|
},
|
|
2218
2221
|
onVisibilityHidden(handler) {
|
|
2219
|
-
if (
|
|
2222
|
+
if (g2.document?.addEventListener) {
|
|
2220
2223
|
const listener = () => {
|
|
2221
|
-
if (
|
|
2224
|
+
if (g2.document?.visibilityState === "hidden") handler();
|
|
2222
2225
|
};
|
|
2223
|
-
|
|
2224
|
-
return () =>
|
|
2226
|
+
g2.document.addEventListener("visibilitychange", listener);
|
|
2227
|
+
return () => g2.document?.removeEventListener?.("visibilitychange", listener);
|
|
2225
2228
|
}
|
|
2226
2229
|
return () => {
|
|
2227
2230
|
};
|
|
2228
2231
|
},
|
|
2229
2232
|
sendBeacon(url, body) {
|
|
2230
|
-
if (typeof
|
|
2233
|
+
if (typeof g2.navigator?.sendBeacon !== "function") return false;
|
|
2231
2234
|
try {
|
|
2232
|
-
return
|
|
2235
|
+
return g2.navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
2233
2236
|
} catch {
|
|
2234
2237
|
return false;
|
|
2235
2238
|
}
|
|
@@ -3906,6 +3909,732 @@ function WatchtowerProvider(props) {
|
|
|
3906
3909
|
return React2__default.default.createElement(React2__default.default.Fragment, null, children);
|
|
3907
3910
|
}
|
|
3908
3911
|
|
|
3912
|
+
// ../watchtower-recorder/dist/index.mjs
|
|
3913
|
+
var CreateElementNode = (id, parentID, index, tag) => [8, id, parentID, index, tag];
|
|
3914
|
+
var CreateTextNode = (id, parentID, index) => [
|
|
3915
|
+
9,
|
|
3916
|
+
id,
|
|
3917
|
+
parentID,
|
|
3918
|
+
index
|
|
3919
|
+
];
|
|
3920
|
+
var MoveNode = (id, parentID, index) => [
|
|
3921
|
+
10,
|
|
3922
|
+
id,
|
|
3923
|
+
parentID,
|
|
3924
|
+
index
|
|
3925
|
+
];
|
|
3926
|
+
var RemoveNode = (id) => [11, id];
|
|
3927
|
+
var SetNodeAttribute = (id, name, value) => [
|
|
3928
|
+
12,
|
|
3929
|
+
id,
|
|
3930
|
+
name,
|
|
3931
|
+
value
|
|
3932
|
+
];
|
|
3933
|
+
var RemoveNodeAttribute = (id, name) => [
|
|
3934
|
+
13,
|
|
3935
|
+
id,
|
|
3936
|
+
name
|
|
3937
|
+
];
|
|
3938
|
+
var SetNodeData = (id, data) => [14, id, data];
|
|
3939
|
+
var MobileTap = (id, hesitationMs, label, selector, normX, normY) => [68, id, hesitationMs, label, selector, normX, normY];
|
|
3940
|
+
var ScreenView = (name, prevName) => [
|
|
3941
|
+
200,
|
|
3942
|
+
name,
|
|
3943
|
+
prevName
|
|
3944
|
+
];
|
|
3945
|
+
var Frustration = (kind, nodeId, selector, count) => [201, kind, nodeId, selector, count];
|
|
3946
|
+
var TapOutcome = (tapNodeId, kind, label, mutationCount) => [202, tapNodeId, kind, label, mutationCount];
|
|
3947
|
+
function walk(root, visit) {
|
|
3948
|
+
const rec = (node, parent, index) => {
|
|
3949
|
+
visit(node, parent, index);
|
|
3950
|
+
node.children.forEach((child, i) => rec(child, node, i));
|
|
3951
|
+
};
|
|
3952
|
+
if (root) rec(root, null, 0);
|
|
3953
|
+
}
|
|
3954
|
+
var Mirror = class {
|
|
3955
|
+
constructor() {
|
|
3956
|
+
__publicField(this, "ids", /* @__PURE__ */ new WeakMap());
|
|
3957
|
+
__publicField(this, "byId", /* @__PURE__ */ new Map());
|
|
3958
|
+
__publicField(this, "next", 1);
|
|
3959
|
+
}
|
|
3960
|
+
// 0 is reserved for "no/root parent"
|
|
3961
|
+
/** Existing id for a node, or undefined if never registered. */
|
|
3962
|
+
peek(key) {
|
|
3963
|
+
return this.ids.get(key);
|
|
3964
|
+
}
|
|
3965
|
+
/** Id for a node, assigning a fresh one on first sight. Second tuple element
|
|
3966
|
+
* is true when the id was just created (→ emit a Create* message). */
|
|
3967
|
+
getId(key) {
|
|
3968
|
+
const existing = this.ids.get(key);
|
|
3969
|
+
if (existing !== void 0) return [existing, false];
|
|
3970
|
+
const id = this.next++;
|
|
3971
|
+
this.ids.set(key, id);
|
|
3972
|
+
this.byId.set(id, key);
|
|
3973
|
+
return [id, true];
|
|
3974
|
+
}
|
|
3975
|
+
getKey(id) {
|
|
3976
|
+
return this.byId.get(id);
|
|
3977
|
+
}
|
|
3978
|
+
/** Drop a node's id (on unmount). Idempotent. */
|
|
3979
|
+
release(key) {
|
|
3980
|
+
const id = this.ids.get(key);
|
|
3981
|
+
if (id === void 0) return void 0;
|
|
3982
|
+
this.ids.delete(key);
|
|
3983
|
+
this.byId.delete(id);
|
|
3984
|
+
return id;
|
|
3985
|
+
}
|
|
3986
|
+
get size() {
|
|
3987
|
+
return this.byId.size;
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
var emptyIndex = () => /* @__PURE__ */ new Map();
|
|
3991
|
+
function indexTree(tree, mirror, prev) {
|
|
3992
|
+
const next = /* @__PURE__ */ new Map();
|
|
3993
|
+
const order = [];
|
|
3994
|
+
walk(tree, (node, parent, idx) => {
|
|
3995
|
+
const [id] = mirror.getId(node.key);
|
|
3996
|
+
const parentId = parent ? mirror.getId(parent.key)[0] : 0;
|
|
3997
|
+
next.set(id, {
|
|
3998
|
+
id,
|
|
3999
|
+
parentId,
|
|
4000
|
+
index: idx,
|
|
4001
|
+
tag: node.tag,
|
|
4002
|
+
attrs: node.attrs,
|
|
4003
|
+
text: node.text
|
|
4004
|
+
});
|
|
4005
|
+
order.push(id);
|
|
4006
|
+
});
|
|
4007
|
+
return { next, order };
|
|
4008
|
+
}
|
|
4009
|
+
function attrDiff(id, prev, next) {
|
|
4010
|
+
const out = [];
|
|
4011
|
+
for (const k of Object.keys(next)) {
|
|
4012
|
+
if (prev[k] !== next[k]) out.push(SetNodeAttribute(id, k, next[k]));
|
|
4013
|
+
}
|
|
4014
|
+
for (const k of Object.keys(prev)) {
|
|
4015
|
+
if (!(k in next)) out.push(RemoveNodeAttribute(id, k));
|
|
4016
|
+
}
|
|
4017
|
+
return out;
|
|
4018
|
+
}
|
|
4019
|
+
function diff(prev, next, mirror) {
|
|
4020
|
+
const { next: nextIndex, order } = indexTree(next, mirror);
|
|
4021
|
+
const messages = [];
|
|
4022
|
+
const removed = /* @__PURE__ */ new Set();
|
|
4023
|
+
for (const id of prev.keys()) if (!nextIndex.has(id)) removed.add(id);
|
|
4024
|
+
for (const id of removed) {
|
|
4025
|
+
const rec = prev.get(id);
|
|
4026
|
+
if (!removed.has(rec.parentId)) messages.push(RemoveNode(id));
|
|
4027
|
+
}
|
|
4028
|
+
for (const id of order) {
|
|
4029
|
+
const rec = nextIndex.get(id);
|
|
4030
|
+
const before = prev.get(id);
|
|
4031
|
+
if (!before) {
|
|
4032
|
+
if (rec.tag === "#text") {
|
|
4033
|
+
messages.push(CreateTextNode(id, rec.parentId, rec.index));
|
|
4034
|
+
} else {
|
|
4035
|
+
messages.push(CreateElementNode(id, rec.parentId, rec.index, rec.tag));
|
|
4036
|
+
}
|
|
4037
|
+
for (const k of Object.keys(rec.attrs)) messages.push(SetNodeAttribute(id, k, rec.attrs[k]));
|
|
4038
|
+
if (rec.text !== void 0 && rec.text !== "") messages.push(SetNodeData(id, rec.text));
|
|
4039
|
+
} else {
|
|
4040
|
+
if (before.parentId !== rec.parentId || before.index !== rec.index) {
|
|
4041
|
+
messages.push(MoveNode(id, rec.parentId, rec.index));
|
|
4042
|
+
}
|
|
4043
|
+
messages.push(...attrDiff(id, before.attrs, rec.attrs));
|
|
4044
|
+
if ((before.text ?? "") !== (rec.text ?? "")) messages.push(SetNodeData(id, rec.text ?? ""));
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
return { messages, index: nextIndex };
|
|
4048
|
+
}
|
|
4049
|
+
function stableAnchor(step) {
|
|
4050
|
+
if (step.attrs.testID) return `@${step.attrs.testID}`;
|
|
4051
|
+
if (step.attrs.nativeID) return `#${step.attrs.nativeID}`;
|
|
4052
|
+
return null;
|
|
4053
|
+
}
|
|
4054
|
+
function computeSelector(path) {
|
|
4055
|
+
if (path.length === 0) return "";
|
|
4056
|
+
const self = path[path.length - 1];
|
|
4057
|
+
const own = stableAnchor(self);
|
|
4058
|
+
if (own) return own;
|
|
4059
|
+
const parts = [];
|
|
4060
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
4061
|
+
const step = path[i];
|
|
4062
|
+
const anchor = stableAnchor(step);
|
|
4063
|
+
if (anchor && i !== path.length - 1) {
|
|
4064
|
+
parts.unshift(anchor);
|
|
4065
|
+
return parts.join(" > ");
|
|
4066
|
+
}
|
|
4067
|
+
const a11y = step.attrs.accessibilityLabel;
|
|
4068
|
+
const comp = step.attrs.rnComponent;
|
|
4069
|
+
if (i === path.length - 1 && a11y) {
|
|
4070
|
+
parts.unshift(`${step.tag}[a11y="${truncate(a11y)}"]`);
|
|
4071
|
+
} else if (comp && comp !== step.tag) {
|
|
4072
|
+
parts.unshift(`${comp}`);
|
|
4073
|
+
} else {
|
|
4074
|
+
parts.unshift(`${step.tag}:nth-of-type(${step.nthOfType})`);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
return parts.join(" > ");
|
|
4078
|
+
}
|
|
4079
|
+
function truncate(s, n = 40) {
|
|
4080
|
+
return s.length > n ? s.slice(0, n) : s;
|
|
4081
|
+
}
|
|
4082
|
+
function computeLabel(attrs, text) {
|
|
4083
|
+
if (attrs.accessibilityLabel) return truncate(attrs.accessibilityLabel, 100);
|
|
4084
|
+
if (attrs.testID) return attrs.testID;
|
|
4085
|
+
if (text && text.trim()) return truncate(text.trim(), 100);
|
|
4086
|
+
if (attrs.rnComponent) return attrs.rnComponent;
|
|
4087
|
+
return "";
|
|
4088
|
+
}
|
|
4089
|
+
var RAGE_WINDOW_MS = 800;
|
|
4090
|
+
var RAGE_MIN_COUNT = 3;
|
|
4091
|
+
var OUTCOME_WINDOW_MS = 1e3;
|
|
4092
|
+
var TOGGLE_ATTR_MAX = 4;
|
|
4093
|
+
var OUTCOME_LABELS = {
|
|
4094
|
+
navigated: "Navigated to a new screen",
|
|
4095
|
+
opened_overlay: "Opened a modal / overlay",
|
|
4096
|
+
closed_overlay: "Dismissed a modal / overlay",
|
|
4097
|
+
toggled: "Toggled UI state",
|
|
4098
|
+
content_changed: "Updated on-screen content",
|
|
4099
|
+
no_effect: "Nothing changed (dead tap)"
|
|
4100
|
+
};
|
|
4101
|
+
var InteractionAnalyzer = class {
|
|
4102
|
+
constructor() {
|
|
4103
|
+
__publicField(this, "activity", []);
|
|
4104
|
+
__publicField(this, "screenViews", []);
|
|
4105
|
+
__publicField(this, "taps", []);
|
|
4106
|
+
}
|
|
4107
|
+
recordActivity(a) {
|
|
4108
|
+
this.activity.push(a);
|
|
4109
|
+
}
|
|
4110
|
+
recordScreenView(t) {
|
|
4111
|
+
this.screenViews.push(t);
|
|
4112
|
+
}
|
|
4113
|
+
/** Register a tap; returns whether it is (so far) a rage tap and the count. */
|
|
4114
|
+
recordTap(tap) {
|
|
4115
|
+
this.taps.push(tap);
|
|
4116
|
+
const from = tap.t - RAGE_WINDOW_MS;
|
|
4117
|
+
const sameSel = this.taps.filter((x) => x.selector === tap.selector && x.t >= from && x.t <= tap.t);
|
|
4118
|
+
return { rage: sameSel.length >= RAGE_MIN_COUNT, count: sameSel.length };
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Evaluate a tap once its outcome window has closed. Classifies the post-tap
|
|
4122
|
+
* mutation diff into a semantic outcome and flags dead/rage.
|
|
4123
|
+
*/
|
|
4124
|
+
evaluate(tap) {
|
|
4125
|
+
const end = tap.t + OUTCOME_WINDOW_MS;
|
|
4126
|
+
const win = this.activity.filter((a) => a.t > tap.t && a.t <= end);
|
|
4127
|
+
const navigated = this.screenViews.some((t) => t > tap.t && t <= end);
|
|
4128
|
+
const sum = (f) => win.reduce((n, a) => n + f(a), 0);
|
|
4129
|
+
const structural = sum((a) => a.create) + sum((a) => a.remove) + sum((a) => a.move);
|
|
4130
|
+
const attrs = sum((a) => a.attr) + sum((a) => a.text);
|
|
4131
|
+
const mutationCount = structural + attrs;
|
|
4132
|
+
const overlayAdd = win.some((a) => a.overlayAdd);
|
|
4133
|
+
const overlayRemove = win.some((a) => a.overlayRemove);
|
|
4134
|
+
let kind;
|
|
4135
|
+
if (navigated) kind = "navigated";
|
|
4136
|
+
else if (overlayAdd) kind = "opened_overlay";
|
|
4137
|
+
else if (overlayRemove) kind = "closed_overlay";
|
|
4138
|
+
else if (mutationCount === 0) kind = "no_effect";
|
|
4139
|
+
else if (structural === 0 && attrs <= TOGGLE_ATTR_MAX) kind = "toggled";
|
|
4140
|
+
else kind = "content_changed";
|
|
4141
|
+
const from = tap.t - RAGE_WINDOW_MS;
|
|
4142
|
+
const rageCount = this.taps.filter(
|
|
4143
|
+
(x) => x.selector === tap.selector && x.t >= from && x.t <= tap.t
|
|
4144
|
+
).length;
|
|
4145
|
+
let frustration = null;
|
|
4146
|
+
if (kind === "no_effect") frustration = { kind: "dead", count: 1 };
|
|
4147
|
+
if (rageCount >= RAGE_MIN_COUNT) frustration = { kind: "rage", count: rageCount };
|
|
4148
|
+
return { frustration, outcome: { kind, label: OUTCOME_LABELS[kind], mutationCount } };
|
|
4149
|
+
}
|
|
4150
|
+
};
|
|
4151
|
+
var OVERLAY_TAGS = /* @__PURE__ */ new Set(["Modal"]);
|
|
4152
|
+
function isOverlayRecord(tag, attrs) {
|
|
4153
|
+
if (tag && OVERLAY_TAGS.has(tag)) return true;
|
|
4154
|
+
if (attrs && (attrs.wtOverlay === "true" || attrs.role === "dialog")) return true;
|
|
4155
|
+
return false;
|
|
4156
|
+
}
|
|
4157
|
+
var Recorder = class {
|
|
4158
|
+
constructor() {
|
|
4159
|
+
__publicField(this, "mirror", new Mirror());
|
|
4160
|
+
__publicField(this, "index", emptyIndex());
|
|
4161
|
+
__publicField(this, "analyzer", new InteractionAnalyzer());
|
|
4162
|
+
__publicField(this, "stream", []);
|
|
4163
|
+
__publicField(this, "pending", []);
|
|
4164
|
+
__publicField(this, "viewport", { w: 0, h: 0 });
|
|
4165
|
+
}
|
|
4166
|
+
emit(t, m) {
|
|
4167
|
+
this.stream.push({ t, m });
|
|
4168
|
+
}
|
|
4169
|
+
setViewport(w, h, t) {
|
|
4170
|
+
this.viewport = { w, h };
|
|
4171
|
+
this.emit(t, [5, w, h]);
|
|
4172
|
+
}
|
|
4173
|
+
/** Feed a fresh tree snapshot; diffs against the last and streams mutations. */
|
|
4174
|
+
commit(tree, t) {
|
|
4175
|
+
const prev = this.index;
|
|
4176
|
+
const { messages, index } = diff(prev, tree, this.mirror);
|
|
4177
|
+
for (const m of messages) this.emit(t, m);
|
|
4178
|
+
const newIds = /* @__PURE__ */ new Set();
|
|
4179
|
+
for (const m of messages) {
|
|
4180
|
+
if (m[0] === 8 || m[0] === 9) newIds.add(m[1]);
|
|
4181
|
+
}
|
|
4182
|
+
let create = 0, remove = 0, move = 0, attr = 0, text = 0;
|
|
4183
|
+
let overlayAdd = false, overlayRemove = false;
|
|
4184
|
+
for (const m of messages) {
|
|
4185
|
+
switch (m[0]) {
|
|
4186
|
+
case 8:
|
|
4187
|
+
create++;
|
|
4188
|
+
if (isOverlayRecord(index.get(m[1])?.tag, index.get(m[1])?.attrs)) overlayAdd = true;
|
|
4189
|
+
break;
|
|
4190
|
+
case 9:
|
|
4191
|
+
create++;
|
|
4192
|
+
break;
|
|
4193
|
+
case 11:
|
|
4194
|
+
remove++;
|
|
4195
|
+
if (isOverlayRecord(prev.get(m[1])?.tag, prev.get(m[1])?.attrs)) overlayRemove = true;
|
|
4196
|
+
break;
|
|
4197
|
+
case 10:
|
|
4198
|
+
move++;
|
|
4199
|
+
break;
|
|
4200
|
+
case 12:
|
|
4201
|
+
case 13:
|
|
4202
|
+
if (!newIds.has(m[1])) attr++;
|
|
4203
|
+
break;
|
|
4204
|
+
case 14:
|
|
4205
|
+
if (!newIds.has(m[1])) text++;
|
|
4206
|
+
break;
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
const activity = { t, create, remove, move, attr, text, overlayAdd, overlayRemove };
|
|
4210
|
+
this.analyzer.recordActivity(activity);
|
|
4211
|
+
this.index = index;
|
|
4212
|
+
this.drainPending(t);
|
|
4213
|
+
}
|
|
4214
|
+
tap(input, t) {
|
|
4215
|
+
const [id] = this.mirror.getId(input.key);
|
|
4216
|
+
const selector = computeSelector(input.path);
|
|
4217
|
+
const label = computeLabel(input.attrs, input.text);
|
|
4218
|
+
const nx = Math.round(input.normX * 1e4);
|
|
4219
|
+
const ny = Math.round(input.normY * 1e4);
|
|
4220
|
+
this.emit(t, MobileTap(id, input.hesitationMs ?? 0, label, selector, nx, ny));
|
|
4221
|
+
this.analyzer.recordTap({ t, nodeId: id, selector });
|
|
4222
|
+
this.pending.push({ t, nodeId: id, selector });
|
|
4223
|
+
this.drainPending(t);
|
|
4224
|
+
}
|
|
4225
|
+
screenView(name, prevName, t) {
|
|
4226
|
+
this.emit(t, ScreenView(name, prevName));
|
|
4227
|
+
this.analyzer.recordScreenView(t);
|
|
4228
|
+
this.drainPending(t);
|
|
4229
|
+
}
|
|
4230
|
+
/** Evaluate any pending taps whose outcome window has closed by time `t`. */
|
|
4231
|
+
drainPending(t) {
|
|
4232
|
+
const ready = this.pending.filter((p) => t >= p.t + OUTCOME_WINDOW_MS);
|
|
4233
|
+
if (!ready.length) return;
|
|
4234
|
+
this.pending = this.pending.filter((p) => t < p.t + OUTCOME_WINDOW_MS);
|
|
4235
|
+
for (const p of ready) this.evaluateTap(p);
|
|
4236
|
+
}
|
|
4237
|
+
evaluateTap(p) {
|
|
4238
|
+
const at = p.t + OUTCOME_WINDOW_MS;
|
|
4239
|
+
const v = this.analyzer.evaluate(p);
|
|
4240
|
+
if (v.frustration) {
|
|
4241
|
+
this.emit(at, Frustration(v.frustration.kind, p.nodeId, p.selector, v.frustration.count));
|
|
4242
|
+
}
|
|
4243
|
+
this.emit(at, TapOutcome(p.nodeId, v.outcome.kind, v.outcome.label, v.outcome.mutationCount));
|
|
4244
|
+
}
|
|
4245
|
+
/** Explicit clock advance (adapter ticker) to close outcome windows even when
|
|
4246
|
+
* no commit/tap arrives. */
|
|
4247
|
+
advance(t) {
|
|
4248
|
+
this.drainPending(t);
|
|
4249
|
+
}
|
|
4250
|
+
/** Flush all remaining pending taps (session end). `_t` is the session-end
|
|
4251
|
+
* clock, accepted for API symmetry with advance(); evaluation uses each tap's
|
|
4252
|
+
* own outcome window. */
|
|
4253
|
+
finalize(_t) {
|
|
4254
|
+
const ready = this.pending;
|
|
4255
|
+
this.pending = [];
|
|
4256
|
+
for (const p of ready) this.evaluateTap(p);
|
|
4257
|
+
this.stream.sort((a, b) => a.t - b.t);
|
|
4258
|
+
}
|
|
4259
|
+
getStream() {
|
|
4260
|
+
return [...this.stream].sort((a, b) => a.t - b.t);
|
|
4261
|
+
}
|
|
4262
|
+
get viewportSize() {
|
|
4263
|
+
return this.viewport;
|
|
4264
|
+
}
|
|
4265
|
+
};
|
|
4266
|
+
|
|
4267
|
+
// src/react-native/structural/install-hook.ts
|
|
4268
|
+
var g = globalThis;
|
|
4269
|
+
var hookState = g.__SITEPONG_STRUCTURAL_HOOK_STATE__ ?? (g.__SITEPONG_STRUCTURAL_HOOK_STATE__ = {
|
|
4270
|
+
latestRoot: null,
|
|
4271
|
+
dirty: false
|
|
4272
|
+
});
|
|
4273
|
+
function record(root) {
|
|
4274
|
+
hookState.latestRoot = root;
|
|
4275
|
+
hookState.dirty = true;
|
|
4276
|
+
}
|
|
4277
|
+
function installStructuralHook() {
|
|
4278
|
+
if (g.__SITEPONG_STRUCTURAL_HOOK_INSTALLED__) return;
|
|
4279
|
+
const existing = g.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
4280
|
+
if (existing) {
|
|
4281
|
+
const prevRaw = existing.onCommitFiberRoot;
|
|
4282
|
+
const prev = typeof prevRaw === "function" ? prevRaw.bind(existing) : null;
|
|
4283
|
+
existing.onCommitFiberRoot = (id, root, ...rest) => {
|
|
4284
|
+
record(root);
|
|
4285
|
+
return prev ? prev(id, root, ...rest) : void 0;
|
|
4286
|
+
};
|
|
4287
|
+
} else {
|
|
4288
|
+
const renderers = /* @__PURE__ */ new Map();
|
|
4289
|
+
g.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
4290
|
+
renderers,
|
|
4291
|
+
supportsFiber: true,
|
|
4292
|
+
inject(renderer) {
|
|
4293
|
+
const id = renderers.size + 1;
|
|
4294
|
+
renderers.set(id, renderer);
|
|
4295
|
+
return id;
|
|
4296
|
+
},
|
|
4297
|
+
onCommitFiberRoot(_id, root) {
|
|
4298
|
+
record(root);
|
|
4299
|
+
},
|
|
4300
|
+
onCommitFiberUnmount() {
|
|
4301
|
+
},
|
|
4302
|
+
onScheduleFiberRoot() {
|
|
4303
|
+
},
|
|
4304
|
+
on() {
|
|
4305
|
+
},
|
|
4306
|
+
off() {
|
|
4307
|
+
},
|
|
4308
|
+
sub() {
|
|
4309
|
+
return () => {
|
|
4310
|
+
};
|
|
4311
|
+
},
|
|
4312
|
+
checkDCE() {
|
|
4313
|
+
}
|
|
4314
|
+
};
|
|
4315
|
+
}
|
|
4316
|
+
g.__SITEPONG_STRUCTURAL_HOOK_INSTALLED__ = true;
|
|
4317
|
+
}
|
|
4318
|
+
var HostComponent = 5;
|
|
4319
|
+
var HostText = 6;
|
|
4320
|
+
function mapTag(type) {
|
|
4321
|
+
if (typeof type !== "string") return "View";
|
|
4322
|
+
const t = type;
|
|
4323
|
+
if (/TextInput|TextField/i.test(t)) return "TextInput";
|
|
4324
|
+
if (/Image/i.test(t)) return "Image";
|
|
4325
|
+
if (/ScrollView/i.test(t)) return "ScrollView";
|
|
4326
|
+
if (/Modal/i.test(t)) return "Modal";
|
|
4327
|
+
if (/Text|RawText/i.test(t)) return "Text";
|
|
4328
|
+
if (/Pressable|Button|Touchable/i.test(t)) return "Pressable";
|
|
4329
|
+
return "View";
|
|
4330
|
+
}
|
|
4331
|
+
function nativeTagOf(stateNode) {
|
|
4332
|
+
if (!stateNode) return void 0;
|
|
4333
|
+
return stateNode.__nativeTag ?? stateNode._nativeTag ?? stateNode.canonical?.nativeTag ?? stateNode.canonical?._nativeTag ?? (typeof stateNode === "number" ? stateNode : void 0);
|
|
4334
|
+
}
|
|
4335
|
+
var STYLE_KEYS = /* @__PURE__ */ new Set([
|
|
4336
|
+
"flex",
|
|
4337
|
+
"flexDirection",
|
|
4338
|
+
"flexGrow",
|
|
4339
|
+
"flexShrink",
|
|
4340
|
+
"flexWrap",
|
|
4341
|
+
"alignItems",
|
|
4342
|
+
"alignSelf",
|
|
4343
|
+
"justifyContent",
|
|
4344
|
+
"width",
|
|
4345
|
+
"height",
|
|
4346
|
+
"minWidth",
|
|
4347
|
+
"minHeight",
|
|
4348
|
+
"maxWidth",
|
|
4349
|
+
"maxHeight",
|
|
4350
|
+
"margin",
|
|
4351
|
+
"marginTop",
|
|
4352
|
+
"marginBottom",
|
|
4353
|
+
"marginLeft",
|
|
4354
|
+
"marginRight",
|
|
4355
|
+
"marginHorizontal",
|
|
4356
|
+
"marginVertical",
|
|
4357
|
+
"padding",
|
|
4358
|
+
"paddingTop",
|
|
4359
|
+
"paddingBottom",
|
|
4360
|
+
"paddingLeft",
|
|
4361
|
+
"paddingRight",
|
|
4362
|
+
"paddingHorizontal",
|
|
4363
|
+
"paddingVertical",
|
|
4364
|
+
"backgroundColor",
|
|
4365
|
+
"borderRadius",
|
|
4366
|
+
"borderWidth",
|
|
4367
|
+
"borderColor",
|
|
4368
|
+
"opacity",
|
|
4369
|
+
"position",
|
|
4370
|
+
"top",
|
|
4371
|
+
"left",
|
|
4372
|
+
"right",
|
|
4373
|
+
"bottom",
|
|
4374
|
+
"color",
|
|
4375
|
+
"fontSize",
|
|
4376
|
+
"fontWeight",
|
|
4377
|
+
"textAlign",
|
|
4378
|
+
"lineHeight",
|
|
4379
|
+
"gap",
|
|
4380
|
+
"overflow",
|
|
4381
|
+
"shadowColor",
|
|
4382
|
+
"shadowOpacity",
|
|
4383
|
+
"shadowRadius",
|
|
4384
|
+
"elevation",
|
|
4385
|
+
"transform",
|
|
4386
|
+
"zIndex",
|
|
4387
|
+
"aspectRatio"
|
|
4388
|
+
]);
|
|
4389
|
+
function extractStyle(style) {
|
|
4390
|
+
try {
|
|
4391
|
+
const flat = reactNative.StyleSheet.flatten(style);
|
|
4392
|
+
if (!flat) return void 0;
|
|
4393
|
+
const out = {};
|
|
4394
|
+
for (const k of Object.keys(flat)) if (STYLE_KEYS.has(k)) out[k] = flat[k];
|
|
4395
|
+
return Object.keys(out).length ? JSON.stringify(out) : void 0;
|
|
4396
|
+
} catch {
|
|
4397
|
+
return void 0;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
function extractAttrs(tag, props, componentName2) {
|
|
4401
|
+
const attrs = {};
|
|
4402
|
+
if (!props) return attrs;
|
|
4403
|
+
if (props.testID) attrs.testID = String(props.testID);
|
|
4404
|
+
if (props.nativeID) attrs.nativeID = String(props.nativeID);
|
|
4405
|
+
if (props.accessibilityLabel) attrs.accessibilityLabel = String(props.accessibilityLabel);
|
|
4406
|
+
if (props.accessibilityRole) attrs.role = String(props.accessibilityRole);
|
|
4407
|
+
if (props["aria-label"]) attrs.accessibilityLabel = String(props["aria-label"]);
|
|
4408
|
+
const style = extractStyle(props.style);
|
|
4409
|
+
if (style) attrs.style = style;
|
|
4410
|
+
if (tag === "Image") {
|
|
4411
|
+
const uri = props.source?.uri ?? (typeof props.source === "string" ? props.source : void 0);
|
|
4412
|
+
if (uri) attrs.src = String(uri);
|
|
4413
|
+
}
|
|
4414
|
+
if (componentName2) attrs.rnComponent = componentName2;
|
|
4415
|
+
if (props.wtOverlay || props.accessibilityViewIsModal) attrs.wtOverlay = "true";
|
|
4416
|
+
if (props.wtSensitive || props.secureTextEntry) attrs.wtSensitive = "true";
|
|
4417
|
+
return attrs;
|
|
4418
|
+
}
|
|
4419
|
+
function collectText(fiber) {
|
|
4420
|
+
let out = "";
|
|
4421
|
+
const visit = (f) => {
|
|
4422
|
+
if (!f) return;
|
|
4423
|
+
if (f.tag === HostText) {
|
|
4424
|
+
const p = f.memoizedProps;
|
|
4425
|
+
if (typeof p === "string") out += p;
|
|
4426
|
+
else if (p && typeof p.text === "string") out += p.text;
|
|
4427
|
+
} else if (f.tag === HostComponent && typeof f.type === "string" && /RawText|VirtualText/i.test(f.type)) {
|
|
4428
|
+
const p = f.memoizedProps;
|
|
4429
|
+
if (p && typeof p.text === "string") out += p.text;
|
|
4430
|
+
}
|
|
4431
|
+
visit(f.child);
|
|
4432
|
+
visit(f.sibling);
|
|
4433
|
+
};
|
|
4434
|
+
visit(fiber.child);
|
|
4435
|
+
return out;
|
|
4436
|
+
}
|
|
4437
|
+
function buildHostChildren(fiber, inheritedName, index, path) {
|
|
4438
|
+
const out = [];
|
|
4439
|
+
let f = fiber;
|
|
4440
|
+
while (f) {
|
|
4441
|
+
collectFrom(f, inheritedName, out, index, path);
|
|
4442
|
+
f = f.sibling;
|
|
4443
|
+
}
|
|
4444
|
+
return out;
|
|
4445
|
+
}
|
|
4446
|
+
function componentName(fiber) {
|
|
4447
|
+
const t = fiber?.type;
|
|
4448
|
+
if (typeof t === "function") return t.displayName || t.name || void 0;
|
|
4449
|
+
if (t && typeof t === "object") return t.displayName || void 0;
|
|
4450
|
+
return void 0;
|
|
4451
|
+
}
|
|
4452
|
+
function collectFrom(fiber, inheritedName, out, index, parentPath) {
|
|
4453
|
+
if (!fiber) return;
|
|
4454
|
+
if (fiber.tag === HostComponent) {
|
|
4455
|
+
const tag = mapTag(fiber.type);
|
|
4456
|
+
const attrs = extractAttrs(tag, fiber.memoizedProps, inheritedName);
|
|
4457
|
+
const stateNode = fiber.stateNode;
|
|
4458
|
+
const key = stateNode && typeof stateNode === "object" ? stateNode : fiber;
|
|
4459
|
+
const isTextLeaf = tag === "Text";
|
|
4460
|
+
const text = isTextLeaf ? collectText(fiber) : void 0;
|
|
4461
|
+
const nthOfType = out.filter((n) => n.tag === tag).length + 1;
|
|
4462
|
+
const myPath = [...parentPath, { tag, attrs, nthOfType }];
|
|
4463
|
+
const children = isTextLeaf ? [] : buildHostChildren(fiber.child, void 0, index, myPath);
|
|
4464
|
+
const vnode = { key, tag, attrs, children, ...text ? { text } : {} };
|
|
4465
|
+
out.push(vnode);
|
|
4466
|
+
const nt = nativeTagOf(stateNode);
|
|
4467
|
+
if (nt !== void 0) index.set(nt, { node: vnode, path: myPath });
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
const name = componentName(fiber) ?? inheritedName;
|
|
4471
|
+
let child = fiber.child;
|
|
4472
|
+
while (child) {
|
|
4473
|
+
collectFrom(child, name, out, index, parentPath);
|
|
4474
|
+
child = child.sibling;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
function walkFiberRoot(root) {
|
|
4478
|
+
const index = /* @__PURE__ */ new Map();
|
|
4479
|
+
const current = root?.current;
|
|
4480
|
+
if (!current) return { tree: null, index };
|
|
4481
|
+
const rootKey = root ?? current;
|
|
4482
|
+
const children = buildHostChildren(current.child, void 0, index, []);
|
|
4483
|
+
const tree = { key: rootKey, tag: "View", attrs: { style: JSON.stringify({ flex: 1 }) }, children };
|
|
4484
|
+
return { tree, index };
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
// src/react-native/structural/capture.ts
|
|
4488
|
+
function genSessionId() {
|
|
4489
|
+
const rand = Math.floor(Math.random() * 16777215).toString(36);
|
|
4490
|
+
return `wt-${Date.now().toString(36)}-${rand}`;
|
|
4491
|
+
}
|
|
4492
|
+
var StructuralCapture = class {
|
|
4493
|
+
constructor() {
|
|
4494
|
+
this.recorder = new Recorder();
|
|
4495
|
+
this.currentIndex = /* @__PURE__ */ new Map();
|
|
4496
|
+
this.started = 0;
|
|
4497
|
+
this.sampleTimer = null;
|
|
4498
|
+
this.flushTimer = null;
|
|
4499
|
+
this.screen = "";
|
|
4500
|
+
this.prevScreen = "";
|
|
4501
|
+
this.cfg = null;
|
|
4502
|
+
this.sessionId = "";
|
|
4503
|
+
this.running = false;
|
|
4504
|
+
}
|
|
4505
|
+
now() {
|
|
4506
|
+
return Date.now() - this.started;
|
|
4507
|
+
}
|
|
4508
|
+
isRunning() {
|
|
4509
|
+
return this.running;
|
|
4510
|
+
}
|
|
4511
|
+
getSessionId() {
|
|
4512
|
+
return this.sessionId;
|
|
4513
|
+
}
|
|
4514
|
+
start(cfg) {
|
|
4515
|
+
if (this.running) return;
|
|
4516
|
+
installStructuralHook();
|
|
4517
|
+
this.cfg = cfg;
|
|
4518
|
+
this.sessionId = cfg.sessionId || genSessionId();
|
|
4519
|
+
this.started = Date.now();
|
|
4520
|
+
this.running = true;
|
|
4521
|
+
const { width, height } = reactNative.Dimensions.get("window");
|
|
4522
|
+
this.recorder.setViewport(Math.round(width), Math.round(height), 0);
|
|
4523
|
+
this.sampleTimer = setInterval(() => this.sample(), cfg.sampleIntervalMs ?? 120);
|
|
4524
|
+
this.flushTimer = setInterval(() => void this.flush(), cfg.flushIntervalMs ?? 4e3);
|
|
4525
|
+
if (cfg.debug) console.log("[structural] started", this.sessionId);
|
|
4526
|
+
setTimeout(() => this.sample(true), 0);
|
|
4527
|
+
}
|
|
4528
|
+
sample(force = false) {
|
|
4529
|
+
if (!this.running) return;
|
|
4530
|
+
if (!force && !hookState.dirty) return;
|
|
4531
|
+
hookState.dirty = false;
|
|
4532
|
+
const root = hookState.latestRoot;
|
|
4533
|
+
if (!root) return;
|
|
4534
|
+
try {
|
|
4535
|
+
const { tree, index } = walkFiberRoot(root);
|
|
4536
|
+
this.currentIndex = index;
|
|
4537
|
+
this.recorder.commit(tree, this.now());
|
|
4538
|
+
} catch (e) {
|
|
4539
|
+
if (this.cfg?.debug) console.log("[structural] sample error", String(e));
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
setScreen(name) {
|
|
4543
|
+
if (!this.running || name === this.screen) return;
|
|
4544
|
+
this.prevScreen = this.screen;
|
|
4545
|
+
this.screen = name;
|
|
4546
|
+
this.sample(true);
|
|
4547
|
+
this.recorder.screenView(name, this.prevScreen, this.now());
|
|
4548
|
+
}
|
|
4549
|
+
/** Resolve a native touch to a node + emit a tap. `target` is the RN
|
|
4550
|
+
* nativeTag from `nativeEvent.target`. */
|
|
4551
|
+
handleTap(target, pageX, pageY) {
|
|
4552
|
+
if (!this.running) return;
|
|
4553
|
+
const entry = this.currentIndex.get(target);
|
|
4554
|
+
if (!entry) return;
|
|
4555
|
+
const { width, height } = reactNative.Dimensions.get("window");
|
|
4556
|
+
this.recorder.tap(
|
|
4557
|
+
{
|
|
4558
|
+
key: entry.node.key,
|
|
4559
|
+
path: entry.path,
|
|
4560
|
+
attrs: entry.node.attrs,
|
|
4561
|
+
text: entry.node.text,
|
|
4562
|
+
normX: pageX / (width || 1),
|
|
4563
|
+
normY: pageY / (height || 1)
|
|
4564
|
+
},
|
|
4565
|
+
this.now()
|
|
4566
|
+
);
|
|
4567
|
+
}
|
|
4568
|
+
async flush() {
|
|
4569
|
+
if (!this.running || !this.cfg) return;
|
|
4570
|
+
this.recorder.advance(this.now());
|
|
4571
|
+
const messages = this.recorder.getStream();
|
|
4572
|
+
if (messages.length === 0) return;
|
|
4573
|
+
const vp = this.recorder.viewportSize;
|
|
4574
|
+
try {
|
|
4575
|
+
const headers = {
|
|
4576
|
+
"content-type": "application/json",
|
|
4577
|
+
"X-API-Key": this.cfg.apiKey
|
|
4578
|
+
};
|
|
4579
|
+
if (this.cfg.projectId) headers["X-Project-ID"] = this.cfg.projectId;
|
|
4580
|
+
await fetch(`${this.cfg.endpoint.replace(/\/$/, "")}/api/replay-stream`, {
|
|
4581
|
+
method: "POST",
|
|
4582
|
+
headers,
|
|
4583
|
+
body: JSON.stringify({
|
|
4584
|
+
session_id: this.sessionId,
|
|
4585
|
+
platform: this.cfg.platform,
|
|
4586
|
+
viewport: { w: vp.w, h: vp.h },
|
|
4587
|
+
messages
|
|
4588
|
+
})
|
|
4589
|
+
});
|
|
4590
|
+
if (this.cfg.debug) console.log("[structural] flushed", messages.length, "messages");
|
|
4591
|
+
} catch (e) {
|
|
4592
|
+
if (this.cfg.debug) console.log("[structural] flush failed", String(e));
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
async stop() {
|
|
4596
|
+
if (!this.running) return;
|
|
4597
|
+
this.recorder.finalize(this.now());
|
|
4598
|
+
if (this.sampleTimer) clearInterval(this.sampleTimer);
|
|
4599
|
+
if (this.flushTimer) clearInterval(this.flushTimer);
|
|
4600
|
+
this.sampleTimer = null;
|
|
4601
|
+
this.flushTimer = null;
|
|
4602
|
+
this.running = false;
|
|
4603
|
+
await this.flush();
|
|
4604
|
+
}
|
|
4605
|
+
};
|
|
4606
|
+
var structuralCapture = new StructuralCapture();
|
|
4607
|
+
var TAP_MOVE_THRESHOLD_PX = 12;
|
|
4608
|
+
function StructuralCaptureProvider({ children, ...cfg }) {
|
|
4609
|
+
React2.useEffect(() => {
|
|
4610
|
+
structuralCapture.start(cfg);
|
|
4611
|
+
return () => {
|
|
4612
|
+
void structuralCapture.stop();
|
|
4613
|
+
};
|
|
4614
|
+
}, []);
|
|
4615
|
+
const startPos = React2.useRef(null);
|
|
4616
|
+
const onTouchStart = (e) => {
|
|
4617
|
+
const { pageX, pageY } = e.nativeEvent;
|
|
4618
|
+
startPos.current = { x: pageX, y: pageY };
|
|
4619
|
+
};
|
|
4620
|
+
const onTouchEnd = (e) => {
|
|
4621
|
+
const ne = e.nativeEvent;
|
|
4622
|
+
const s = startPos.current;
|
|
4623
|
+
startPos.current = null;
|
|
4624
|
+
if (!s) return;
|
|
4625
|
+
if (Math.abs(ne.pageX - s.x) > TAP_MOVE_THRESHOLD_PX || Math.abs(ne.pageY - s.y) > TAP_MOVE_THRESHOLD_PX) {
|
|
4626
|
+
return;
|
|
4627
|
+
}
|
|
4628
|
+
structuralCapture.handleTap(Number(ne.target), ne.pageX, ne.pageY);
|
|
4629
|
+
};
|
|
4630
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { flex: 1 }, onTouchStart, onTouchEnd, children });
|
|
4631
|
+
}
|
|
4632
|
+
function useStructuralScreen(name) {
|
|
4633
|
+
React2.useEffect(() => {
|
|
4634
|
+
structuralCapture.setScreen(name);
|
|
4635
|
+
}, [name]);
|
|
4636
|
+
}
|
|
4637
|
+
|
|
3909
4638
|
// src/react-native/sqlite-tracker.ts
|
|
3910
4639
|
function now() {
|
|
3911
4640
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
@@ -4326,6 +5055,8 @@ exports.RNAutocaptureModule = RNAutocaptureModule;
|
|
|
4326
5055
|
exports.RNPerformanceManager = RNPerformanceManager;
|
|
4327
5056
|
exports.SensitiveView = SensitiveView;
|
|
4328
5057
|
exports.SitePongRNProvider = SitePongRNProvider;
|
|
5058
|
+
exports.StructuralCapture = StructuralCapture;
|
|
5059
|
+
exports.StructuralCaptureProvider = StructuralCaptureProvider;
|
|
4329
5060
|
exports.WatchtowerProvider = WatchtowerProvider;
|
|
4330
5061
|
exports.addBreadcrumb = addBreadcrumb;
|
|
4331
5062
|
exports.addSensitiveView = addSensitiveView;
|
|
@@ -4360,6 +5091,7 @@ exports.handleUniversalLink = handleUniversalLink;
|
|
|
4360
5091
|
exports.identify = identify2;
|
|
4361
5092
|
exports.initRN = initRN;
|
|
4362
5093
|
exports.initSuperLinkRN = initSuperLinkRN;
|
|
5094
|
+
exports.installStructuralHook = installStructuralHook;
|
|
4363
5095
|
exports.isInitialized = isInitialized;
|
|
4364
5096
|
exports.isRemoteConfigFeatureEnabled = isRemoteConfigFeatureEnabled;
|
|
4365
5097
|
exports.isScreenRecording = isScreenRecording;
|
|
@@ -4391,6 +5123,7 @@ exports.startWatchtower = startWatchtower;
|
|
|
4391
5123
|
exports.stopScreenRecording = stopScreenRecording;
|
|
4392
5124
|
exports.stopWatchtower = stopWatchtower;
|
|
4393
5125
|
exports.stopWidgetSync = stopWidgetSync;
|
|
5126
|
+
exports.structuralCapture = structuralCapture;
|
|
4394
5127
|
exports.subscribeNavigationToWatchtower = subscribeNavigationToWatchtower;
|
|
4395
5128
|
exports.superlinkIdentify = identify3;
|
|
4396
5129
|
exports.syncWidgets = syncWidgets;
|
|
@@ -4403,6 +5136,7 @@ exports.useRNPerformance = useRNPerformance;
|
|
|
4403
5136
|
exports.useRemoteConfig = useRemoteConfig;
|
|
4404
5137
|
exports.useScreenTrack = useScreenTrack;
|
|
4405
5138
|
exports.useSitePongRNContext = useSitePongRNContext;
|
|
5139
|
+
exports.useStructuralScreen = useStructuralScreen;
|
|
4406
5140
|
exports.waitForFlags = waitForFlags;
|
|
4407
5141
|
//# sourceMappingURL=rn.js.map
|
|
4408
5142
|
//# sourceMappingURL=rn.js.map
|