miaoda-expo-devkit 0.1.1-beta.1

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.
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var lgui_control_exports = {};
20
+ __export(lgui_control_exports, {
21
+ setupLGUIMessageControl: () => setupLGUIMessageControl
22
+ });
23
+ module.exports = __toCommonJS(lgui_control_exports);
24
+ var import_css_control = require("./css-control");
25
+ var import_es_toolkit = require("es-toolkit");
26
+ const MD_CONFIG = {
27
+ HOVERED_ATTR: "data-editor-hover",
28
+ ACTIVE_ATTR: "data-editor-active",
29
+ FULL_WIDTH_ATTR: "data-editor-full-width"
30
+ };
31
+ const THROTTLE_INTERVAL = 8;
32
+ const THROTTLE_OPTIONS = {
33
+ edges: ["leading", "trailing"]
34
+ };
35
+ function isEditorMessage(data) {
36
+ return typeof data === "object" && data !== null && typeof data.type === "string" && data.type.startsWith("editor-");
37
+ }
38
+ const SOURCE_PROP = "__jsxsource";
39
+ function getFiber(el) {
40
+ const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
41
+ return key ? el[key] : null;
42
+ }
43
+ function getJsxSource(el) {
44
+ const fiber = getFiber(el);
45
+ if (!fiber) return null;
46
+ const owner = fiber._debugOwner;
47
+ const props = owner?.memoizedProps;
48
+ return props?.[SOURCE_PROP] ?? null;
49
+ }
50
+ function isEditable(el) {
51
+ return !!el && !!getJsxSource(el);
52
+ }
53
+ function collectElementInfo(el) {
54
+ const jsxSource = getJsxSource(el);
55
+ if (!jsxSource) return null;
56
+ const fiber = getFiber(el);
57
+ const owner = fiber?._debugOwner;
58
+ const componentTag = el.tagName.toLowerCase();
59
+ const componentName = owner?.type?.displayName ?? componentTag;
60
+ const componentId = `${jsxSource.fileName}:${jsxSource.lineNumber}:${jsxSource.columnNumber}`;
61
+ const componentPath = jsxSource.fileName;
62
+ const componentLine = String(jsxSource.lineNumber);
63
+ const componentIndex = String(owner?.index ?? 0);
64
+ const firstChild = el.firstChild;
65
+ const isSingleTextNode = el.childNodes.length === 1 && firstChild?.nodeType === Node.TEXT_NODE;
66
+ const componentContent = {
67
+ text: isSingleTextNode ? firstChild.nodeValue ?? "" : void 0
68
+ };
69
+ const rect = el.getBoundingClientRect();
70
+ return {
71
+ rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },
72
+ innerHTML: el.innerHTML,
73
+ data: {
74
+ componentId,
75
+ componentTag,
76
+ componentName,
77
+ componentPath,
78
+ componentLine,
79
+ componentIndex,
80
+ componentContent
81
+ }
82
+ };
83
+ }
84
+ function postToParent(type, target) {
85
+ if (window.parent === window) return;
86
+ window.parent.postMessage(target ? { type, target } : { type }, "*");
87
+ }
88
+ function setElementMeta(el) {
89
+ if (Math.abs(el.getBoundingClientRect().width - window.innerWidth) < 5) {
90
+ el.setAttribute(MD_CONFIG.FULL_WIDTH_ATTR, "true");
91
+ }
92
+ }
93
+ function setHoveredAttr(el) {
94
+ el.setAttribute(MD_CONFIG.HOVERED_ATTR, "true");
95
+ setElementMeta(el);
96
+ }
97
+ function removeHoveredAttr(el) {
98
+ el.removeAttribute(MD_CONFIG.HOVERED_ATTR);
99
+ el.removeAttribute(MD_CONFIG.FULL_WIDTH_ATTR);
100
+ }
101
+ function setActiveAttr(el) {
102
+ el.setAttribute(MD_CONFIG.ACTIVE_ATTR, "true");
103
+ setElementMeta(el);
104
+ }
105
+ function removeActiveAttr(el) {
106
+ el.removeAttribute(MD_CONFIG.ACTIVE_ATTR);
107
+ el.removeAttribute(MD_CONFIG.FULL_WIDTH_ATTR);
108
+ }
109
+ class EditorController {
110
+ constructor() {
111
+ this.hoverNode = null;
112
+ this.activeNode = null;
113
+ this.abortController = null;
114
+ this.removeSelectorStyle = null;
115
+ /** 监听选中节点的 innerHTML / 属性变化 */
116
+ this.activeNodeObserver = null;
117
+ this.onMouseOver = (e) => {
118
+ const node = e.target;
119
+ if (isEditable(node)) this.setHoverNode(node);
120
+ };
121
+ this.onMouseLeave = () => {
122
+ this.setHoverNode(null);
123
+ };
124
+ this.onClick = (e) => {
125
+ e.stopPropagation();
126
+ e.preventDefault();
127
+ const node = e.target;
128
+ if (!isEditable(node)) return;
129
+ this.setActiveNode(node);
130
+ };
131
+ this.onScroll = (0, import_es_toolkit.throttle)(
132
+ () => {
133
+ const target = this.activeNode;
134
+ if (!target) return;
135
+ const rect = target.getBoundingClientRect();
136
+ postToParent("iframe-scroll", {
137
+ rect: {
138
+ left: rect.left,
139
+ top: rect.top,
140
+ width: rect.width,
141
+ height: rect.height
142
+ }
143
+ });
144
+ },
145
+ THROTTLE_INTERVAL,
146
+ THROTTLE_OPTIONS
147
+ );
148
+ }
149
+ init() {
150
+ if (this.abortController) return;
151
+ this.abortController = new AbortController();
152
+ const { signal } = this.abortController;
153
+ const opts = { capture: true, signal };
154
+ this.removeSelectorStyle = (0, import_css_control.injectSelectorModeStyle)();
155
+ document.addEventListener("mouseover", this.onMouseOver, opts);
156
+ document.addEventListener("mouseleave", this.onMouseLeave, opts);
157
+ document.addEventListener("click", this.onClick, opts);
158
+ document.addEventListener("scroll", this.onScroll, opts);
159
+ }
160
+ destroy() {
161
+ if (!this.abortController) return;
162
+ this.abortController.abort();
163
+ this.abortController = null;
164
+ this.stopObservingActiveNode();
165
+ this.setHoverNode(null);
166
+ this.setActiveNode(null);
167
+ this.removeSelectorStyle?.();
168
+ this.removeSelectorStyle = null;
169
+ }
170
+ setHoverNode(node) {
171
+ if (node === this.activeNode) return;
172
+ if (this.hoverNode === node) return;
173
+ if (this.hoverNode) removeHoveredAttr(this.hoverNode);
174
+ this.hoverNode = node;
175
+ if (node) setHoveredAttr(node);
176
+ }
177
+ setActiveNode(node) {
178
+ if (this.activeNode === node) return;
179
+ this.stopObservingActiveNode();
180
+ if (this.activeNode) removeActiveAttr(this.activeNode);
181
+ if (node && node === this.hoverNode) {
182
+ removeHoveredAttr(node);
183
+ this.hoverNode = null;
184
+ }
185
+ this.activeNode = node;
186
+ if (node) {
187
+ setActiveAttr(node);
188
+ this.postActiveInfo();
189
+ this.startObservingActiveNode();
190
+ }
191
+ }
192
+ /** 收集并发送选中节点信息 */
193
+ postActiveInfo() {
194
+ if (!this.activeNode) return;
195
+ const info = collectElementInfo(this.activeNode);
196
+ if (info) postToParent("iframe-target-change", info);
197
+ }
198
+ /** 开始观察选中节点的 innerHTML / 属性变化 */
199
+ startObservingActiveNode() {
200
+ if (!this.activeNode) return;
201
+ this.activeNodeObserver = new MutationObserver(() => this.postActiveInfo());
202
+ this.activeNodeObserver.observe(this.activeNode, {
203
+ attributes: true,
204
+ attributeFilter: ["class"]
205
+ });
206
+ }
207
+ /** 停止观察并清理 */
208
+ stopObservingActiveNode() {
209
+ this.activeNodeObserver?.disconnect();
210
+ this.activeNodeObserver = null;
211
+ }
212
+ }
213
+ let controller = null;
214
+ function onGlobalMessage(e) {
215
+ if (!isEditorMessage(e.data)) return;
216
+ switch (e.data.type) {
217
+ case "editor-inject":
218
+ controller?.init();
219
+ break;
220
+ case "editor-destroy":
221
+ controller?.destroy();
222
+ break;
223
+ }
224
+ }
225
+ function setupLGUIMessageControl() {
226
+ if (typeof document === "undefined" || typeof window === "undefined") return;
227
+ controller = new EditorController();
228
+ window.addEventListener("message", onGlobalMessage);
229
+ }
230
+ // Annotate the CommonJS export names for ESM import in node:
231
+ 0 && (module.exports = {
232
+ setupLGUIMessageControl
233
+ });
234
+ //# sourceMappingURL=lgui-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/stubs/lgui-control.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\nimport { injectSelectorModeStyle } from './css-control';\nimport { throttle, type ThrottleOptions } from 'es-toolkit';\n\n/**\n * LGUI 可视化编辑器 postMessage 控制器。\n *\n * 监听 window 上的 postMessage 消息,实现可视化编辑器与 iframe 内页面的双向通信。\n *\n * 消息格式(父窗口 -> iframe):\n * window.postMessage({ type: 'editor-inject' }, '*') // 初始化编辑器\n * window.postMessage({ type: 'editor-destroy' }, '*') // 销毁编辑器\n *\n * 消息格式(iframe -> 父窗口):\n * parent.postMessage({ type: 'iframe-target-change', target: ElementInfo }, '*')\n *\n */\n\n// ============================================================================\n// 类型定义\n// ============================================================================\n\n/** 父窗口 -> iframe 消息类型 */\ntype IncomingMessage = 'editor-inject' | 'editor-destroy';\n\n/** JSX 源位置信息(由 babel-plugin-jsx-source 注入到 data 属性) */\ninterface JsxSource {\n fileName: string;\n lineNumber: number;\n columnNumber: number;\n}\n\n/** 选中元素的详细信息(iframe -> 父窗口) */\ninterface ElementInfo {\n rect: { left: number; top: number; width: number; height: number };\n innerHTML?: string;\n data?: {\n componentId: string;\n /** DOM 标签名,如 div、span */\n componentTag: string;\n /** React 组件名,如 Button、Text */\n componentName: string;\n componentPath: string;\n componentLine: string;\n /** Fiber 在兄弟节点中的索引 */\n componentIndex: string;\n componentContent: {\n text?: string;\n };\n };\n}\n\n// ============================================================================\n// 常量\n// ============================================================================\n\nconst MD_CONFIG = {\n HOVERED_ATTR: 'data-editor-hover',\n ACTIVE_ATTR: 'data-editor-active',\n FULL_WIDTH_ATTR: 'data-editor-full-width',\n} as const;\n\n/** 节流配置 8ms 约等于 120fps,保证流畅的视觉反馈 */\nconst THROTTLE_INTERVAL: number = 8;\n/** 节流选项:leading 和 trailing 边缘都触发,确保首尾事件不丢失 */\nconst THROTTLE_OPTIONS: ThrottleOptions = {\n edges: ['leading', 'trailing'],\n};\n\n// ============================================================================\n// 工具函数\n// ============================================================================\n\n/** 类型守卫:是否为编辑器消息 */\nfunction isEditorMessage(data: unknown): data is { type: IncomingMessage } {\n return (\n typeof data === 'object' &&\n data !== null &&\n typeof (data as { type?: unknown }).type === 'string' &&\n (data as { type: string }).type.startsWith('editor-')\n );\n}\n\n/** JSX 源信息 prop 名(由 babel-plugin-jsx-source 注入) */\nconst SOURCE_PROP = '__jsxsource';\n\n/** 从 DOM 元素上获取 React Fiber */\nfunction getFiber(el: HTMLElement): Record<string, unknown> | null {\n const key = Object.keys(el).find((k) => k.startsWith('__reactFiber$'));\n return key ? (el as unknown as Record<string, Record<string, unknown>>)[key] : null;\n}\n\n/**\n * 从 DOM 元素获取 JSX 源信息。\n *\n * 只检查直接 _debugOwner 的 memoizedProps,\n * 如果没有 __jsxsource 则视为不可编辑元素(库组件内部节点)。\n */\nfunction getJsxSource(el: HTMLElement): JsxSource | null {\n const fiber = getFiber(el);\n if (!fiber) return null;\n\n const owner = fiber._debugOwner as Record<string, unknown> | null;\n const props = owner?.memoizedProps as Record<string, unknown> | undefined;\n return (props?.[SOURCE_PROP] as JsxSource) ?? null;\n}\n\n/** 是否为可编辑目标(有 __jsxsource) */\nfunction isEditable(el: HTMLElement | null): el is HTMLElement {\n return !!el && !!getJsxSource(el);\n}\n\n/** 收集元素详细信息 */\nfunction collectElementInfo(el: HTMLElement): ElementInfo | null {\n const jsxSource = getJsxSource(el);\n if (!jsxSource) return null;\n const fiber = getFiber(el);\n const owner = fiber?._debugOwner as Record<string, unknown> | null;\n const componentTag = el.tagName.toLowerCase();\n const componentName = (owner?.type as { displayName?: string })?.displayName ?? componentTag;\n const componentId = `${jsxSource.fileName}:${jsxSource.lineNumber}:${jsxSource.columnNumber}`;\n const componentPath = jsxSource.fileName;\n const componentLine = String(jsxSource.lineNumber);\n const componentIndex = String(owner?.index ?? 0);\n const firstChild = el.firstChild;\n const isSingleTextNode = el.childNodes.length === 1 && firstChild?.nodeType === Node.TEXT_NODE;\n const componentContent = {\n text: isSingleTextNode ? (firstChild.nodeValue ?? '') : undefined,\n };\n\n const rect = el.getBoundingClientRect();\n return {\n rect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height },\n innerHTML: el.innerHTML,\n data: {\n componentId,\n componentTag,\n componentName,\n componentPath,\n componentLine,\n componentIndex,\n componentContent,\n },\n };\n}\n\n/** 向父窗口发送消息 */\nfunction postToParent(type: string, target?: ElementInfo): void {\n if (window.parent === window) return;\n window.parent.postMessage(target ? { type, target } : { type }, '*');\n}\n\n// ============================================================================\n// data-* 属性操作(纯函数,可复用)\n// ============================================================================\n\n/** 设置元素 meta 属性(全宽检测等) */\nfunction setElementMeta(el: HTMLElement): void {\n if (Math.abs(el.getBoundingClientRect().width - window.innerWidth) < 5) {\n el.setAttribute(MD_CONFIG.FULL_WIDTH_ATTR, 'true');\n }\n}\n\n/** 设置悬停高亮 */\nfunction setHoveredAttr(el: HTMLElement): void {\n el.setAttribute(MD_CONFIG.HOVERED_ATTR, 'true');\n setElementMeta(el);\n}\n\n/** 移除悬停高亮 */\nfunction removeHoveredAttr(el: HTMLElement): void {\n el.removeAttribute(MD_CONFIG.HOVERED_ATTR);\n el.removeAttribute(MD_CONFIG.FULL_WIDTH_ATTR);\n}\n\n/** 设置选中高亮 */\nfunction setActiveAttr(el: HTMLElement): void {\n el.setAttribute(MD_CONFIG.ACTIVE_ATTR, 'true');\n setElementMeta(el);\n}\n\n/** 移除选中高亮 */\nfunction removeActiveAttr(el: HTMLElement): void {\n el.removeAttribute(MD_CONFIG.ACTIVE_ATTR);\n el.removeAttribute(MD_CONFIG.FULL_WIDTH_ATTR);\n}\n\n// ============================================================================\n// 编辑器控制器(管理事件绑定 + 状态生命周期)\n// ============================================================================\n\nclass EditorController {\n private hoverNode: HTMLElement | null = null;\n private activeNode: HTMLElement | null = null;\n private abortController: AbortController | null = null;\n private removeSelectorStyle: (() => void) | null = null;\n /** 监听选中节点的 innerHTML / 属性变化 */\n private activeNodeObserver: MutationObserver | null = null;\n\n init(): void {\n if (this.abortController) return;\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n const opts = { capture: true, signal };\n\n this.removeSelectorStyle = injectSelectorModeStyle();\n\n document.addEventListener('mouseover', this.onMouseOver, opts);\n document.addEventListener('mouseleave', this.onMouseLeave, opts);\n document.addEventListener('click', this.onClick, opts);\n document.addEventListener('scroll', this.onScroll, opts);\n }\n\n destroy(): void {\n if (!this.abortController) return;\n this.abortController.abort();\n this.abortController = null;\n this.stopObservingActiveNode();\n this.setHoverNode(null);\n this.setActiveNode(null);\n this.removeSelectorStyle?.();\n this.removeSelectorStyle = null;\n }\n\n private setHoverNode(node: HTMLElement | null): void {\n // 选中元素不受 hover 影响\n if (node === this.activeNode) return;\n if (this.hoverNode === node) return;\n if (this.hoverNode) removeHoveredAttr(this.hoverNode);\n this.hoverNode = node;\n if (node) setHoveredAttr(node);\n }\n\n private setActiveNode(node: HTMLElement | null): void {\n if (this.activeNode === node) return;\n // 停止观察旧节点\n this.stopObservingActiveNode();\n if (this.activeNode) removeActiveAttr(this.activeNode);\n // 如果新 active 节点正好是当前 hover 节点,清除 hover 样式\n if (node && node === this.hoverNode) {\n removeHoveredAttr(node);\n this.hoverNode = null;\n }\n this.activeNode = node;\n if (node) {\n setActiveAttr(node);\n this.postActiveInfo();\n this.startObservingActiveNode();\n }\n }\n\n /** 收集并发送选中节点信息 */\n private postActiveInfo(): void {\n if (!this.activeNode) return;\n const info = collectElementInfo(this.activeNode);\n if (info) postToParent('iframe-target-change', info);\n }\n\n /** 开始观察选中节点的 innerHTML / 属性变化 */\n private startObservingActiveNode(): void {\n if (!this.activeNode) return;\n this.activeNodeObserver = new MutationObserver(() => this.postActiveInfo());\n this.activeNodeObserver.observe(this.activeNode, {\n attributes: true,\n attributeFilter: ['class'],\n });\n }\n\n /** 停止观察并清理 */\n private stopObservingActiveNode(): void {\n this.activeNodeObserver?.disconnect();\n this.activeNodeObserver = null;\n }\n\n private onMouseOver = (e: Event): void => {\n const node = e.target as HTMLElement;\n if (isEditable(node)) this.setHoverNode(node);\n };\n\n private onMouseLeave = (): void => {\n this.setHoverNode(null);\n };\n\n private onClick = (e: Event): void => {\n e.stopPropagation();\n e.preventDefault();\n const node = e.target as HTMLElement;\n if (!isEditable(node)) return;\n\n this.setActiveNode(node);\n };\n\n private onScroll = throttle(\n (): void => {\n const target: HTMLElement | null = this.activeNode;\n if (!target) return;\n\n const rect: DOMRect = target.getBoundingClientRect();\n postToParent('iframe-scroll', {\n rect: {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height,\n },\n });\n },\n THROTTLE_INTERVAL,\n THROTTLE_OPTIONS\n );\n}\n\nlet controller: EditorController | null = null;\n\nfunction onGlobalMessage(e: MessageEvent): void {\n if (!isEditorMessage(e.data)) return;\n\n switch (e.data.type) {\n case 'editor-inject':\n controller?.init();\n break;\n case 'editor-destroy':\n controller?.destroy();\n break;\n }\n}\n\n/**\n * 初始化 LGUI 编辑器消息控制器。\n * 仅在 browser 环境下执行,React Native 安全。\n */\nexport function setupLGUIMessageControl(): void {\n // React Native 中 window 存在但 document 不存在,需同时检测\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n controller = new EditorController();\n window.addEventListener('message', onGlobalMessage);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,yBAAwC;AACxC,wBAA+C;AAsD/C,MAAM,YAAY;AAAA,EAChB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,iBAAiB;AACnB;AAGA,MAAM,oBAA4B;AAElC,MAAM,mBAAoC;AAAA,EACxC,OAAO,CAAC,WAAW,UAAU;AAC/B;AAOA,SAAS,gBAAgB,MAAkD;AACzE,SACE,OAAO,SAAS,YAChB,SAAS,QACT,OAAQ,KAA4B,SAAS,YAC5C,KAA0B,KAAK,WAAW,SAAS;AAExD;AAGA,MAAM,cAAc;AAGpB,SAAS,SAAS,IAAiD;AACjE,QAAM,MAAM,OAAO,KAAK,EAAE,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,eAAe,CAAC;AACrE,SAAO,MAAO,GAA0D,GAAG,IAAI;AACjF;AAQA,SAAS,aAAa,IAAmC;AACvD,QAAM,QAAQ,SAAS,EAAE;AACzB,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM;AACpB,QAAM,QAAQ,OAAO;AACrB,SAAQ,QAAQ,WAAW,KAAmB;AAChD;AAGA,SAAS,WAAW,IAA2C;AAC7D,SAAO,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE;AAClC;AAGA,SAAS,mBAAmB,IAAqC;AAC/D,QAAM,YAAY,aAAa,EAAE;AACjC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,QAAQ,SAAS,EAAE;AACzB,QAAM,QAAQ,OAAO;AACrB,QAAM,eAAe,GAAG,QAAQ,YAAY;AAC5C,QAAM,gBAAiB,OAAO,MAAmC,eAAe;AAChF,QAAM,cAAc,GAAG,UAAU,QAAQ,IAAI,UAAU,UAAU,IAAI,UAAU,YAAY;AAC3F,QAAM,gBAAgB,UAAU;AAChC,QAAM,gBAAgB,OAAO,UAAU,UAAU;AACjD,QAAM,iBAAiB,OAAO,OAAO,SAAS,CAAC;AAC/C,QAAM,aAAa,GAAG;AACtB,QAAM,mBAAmB,GAAG,WAAW,WAAW,KAAK,YAAY,aAAa,KAAK;AACrF,QAAM,mBAAmB;AAAA,IACvB,MAAM,mBAAoB,WAAW,aAAa,KAAM;AAAA,EAC1D;AAEA,QAAM,OAAO,GAAG,sBAAsB;AACtC,SAAO;AAAA,IACL,MAAM,EAAE,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO;AAAA,IAC/E,WAAW,GAAG;AAAA,IACd,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,aAAa,MAAc,QAA4B;AAC9D,MAAI,OAAO,WAAW,OAAQ;AAC9B,SAAO,OAAO,YAAY,SAAS,EAAE,MAAM,OAAO,IAAI,EAAE,KAAK,GAAG,GAAG;AACrE;AAOA,SAAS,eAAe,IAAuB;AAC7C,MAAI,KAAK,IAAI,GAAG,sBAAsB,EAAE,QAAQ,OAAO,UAAU,IAAI,GAAG;AACtE,OAAG,aAAa,UAAU,iBAAiB,MAAM;AAAA,EACnD;AACF;AAGA,SAAS,eAAe,IAAuB;AAC7C,KAAG,aAAa,UAAU,cAAc,MAAM;AAC9C,iBAAe,EAAE;AACnB;AAGA,SAAS,kBAAkB,IAAuB;AAChD,KAAG,gBAAgB,UAAU,YAAY;AACzC,KAAG,gBAAgB,UAAU,eAAe;AAC9C;AAGA,SAAS,cAAc,IAAuB;AAC5C,KAAG,aAAa,UAAU,aAAa,MAAM;AAC7C,iBAAe,EAAE;AACnB;AAGA,SAAS,iBAAiB,IAAuB;AAC/C,KAAG,gBAAgB,UAAU,WAAW;AACxC,KAAG,gBAAgB,UAAU,eAAe;AAC9C;AAMA,MAAM,iBAAiB;AAAA,EAAvB;AACE,SAAQ,YAAgC;AACxC,SAAQ,aAAiC;AACzC,SAAQ,kBAA0C;AAClD,SAAQ,sBAA2C;AAEnD;AAAA,SAAQ,qBAA8C;AA6EtD,SAAQ,cAAc,CAAC,MAAmB;AACxC,YAAM,OAAO,EAAE;AACf,UAAI,WAAW,IAAI,EAAG,MAAK,aAAa,IAAI;AAAA,IAC9C;AAEA,SAAQ,eAAe,MAAY;AACjC,WAAK,aAAa,IAAI;AAAA,IACxB;AAEA,SAAQ,UAAU,CAAC,MAAmB;AACpC,QAAE,gBAAgB;AAClB,QAAE,eAAe;AACjB,YAAM,OAAO,EAAE;AACf,UAAI,CAAC,WAAW,IAAI,EAAG;AAEvB,WAAK,cAAc,IAAI;AAAA,IACzB;AAEA,SAAQ,eAAW;AAAA,MACjB,MAAY;AACV,cAAM,SAA6B,KAAK;AACxC,YAAI,CAAC,OAAQ;AAEb,cAAM,OAAgB,OAAO,sBAAsB;AACnD,qBAAa,iBAAiB;AAAA,UAC5B,MAAM;AAAA,YACJ,MAAM,KAAK;AAAA,YACX,KAAK,KAAK;AAAA,YACV,OAAO,KAAK;AAAA,YACZ,QAAQ,KAAK;AAAA,UACf;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,EA9GA,OAAa;AACX,QAAI,KAAK,gBAAiB;AAC1B,SAAK,kBAAkB,IAAI,gBAAgB;AAC3C,UAAM,EAAE,OAAO,IAAI,KAAK;AACxB,UAAM,OAAO,EAAE,SAAS,MAAM,OAAO;AAErC,SAAK,0BAAsB,4CAAwB;AAEnD,aAAS,iBAAiB,aAAa,KAAK,aAAa,IAAI;AAC7D,aAAS,iBAAiB,cAAc,KAAK,cAAc,IAAI;AAC/D,aAAS,iBAAiB,SAAS,KAAK,SAAS,IAAI;AACrD,aAAS,iBAAiB,UAAU,KAAK,UAAU,IAAI;AAAA,EACzD;AAAA,EAEA,UAAgB;AACd,QAAI,CAAC,KAAK,gBAAiB;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,kBAAkB;AACvB,SAAK,wBAAwB;AAC7B,SAAK,aAAa,IAAI;AACtB,SAAK,cAAc,IAAI;AACvB,SAAK,sBAAsB;AAC3B,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAEQ,aAAa,MAAgC;AAEnD,QAAI,SAAS,KAAK,WAAY;AAC9B,QAAI,KAAK,cAAc,KAAM;AAC7B,QAAI,KAAK,UAAW,mBAAkB,KAAK,SAAS;AACpD,SAAK,YAAY;AACjB,QAAI,KAAM,gBAAe,IAAI;AAAA,EAC/B;AAAA,EAEQ,cAAc,MAAgC;AACpD,QAAI,KAAK,eAAe,KAAM;AAE9B,SAAK,wBAAwB;AAC7B,QAAI,KAAK,WAAY,kBAAiB,KAAK,UAAU;AAErD,QAAI,QAAQ,SAAS,KAAK,WAAW;AACnC,wBAAkB,IAAI;AACtB,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,aAAa;AAClB,QAAI,MAAM;AACR,oBAAc,IAAI;AAClB,WAAK,eAAe;AACpB,WAAK,yBAAyB;AAAA,IAChC;AAAA,EACF;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,OAAO,mBAAmB,KAAK,UAAU;AAC/C,QAAI,KAAM,cAAa,wBAAwB,IAAI;AAAA,EACrD;AAAA;AAAA,EAGQ,2BAAiC;AACvC,QAAI,CAAC,KAAK,WAAY;AACtB,SAAK,qBAAqB,IAAI,iBAAiB,MAAM,KAAK,eAAe,CAAC;AAC1E,SAAK,mBAAmB,QAAQ,KAAK,YAAY;AAAA,MAC/C,YAAY;AAAA,MACZ,iBAAiB,CAAC,OAAO;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,0BAAgC;AACtC,SAAK,oBAAoB,WAAW;AACpC,SAAK,qBAAqB;AAAA,EAC5B;AAsCF;AAEA,IAAI,aAAsC;AAE1C,SAAS,gBAAgB,GAAuB;AAC9C,MAAI,CAAC,gBAAgB,EAAE,IAAI,EAAG;AAE9B,UAAQ,EAAE,KAAK,MAAM;AAAA,IACnB,KAAK;AACH,kBAAY,KAAK;AACjB;AAAA,IACF,KAAK;AACH,kBAAY,QAAQ;AACpB;AAAA,EACJ;AACF;AAMO,SAAS,0BAAgC;AAE9C,MAAI,OAAO,aAAa,eAAe,OAAO,WAAW,YAAa;AACtE,eAAa,IAAI,iBAAiB;AAClC,SAAO,iBAAiB,WAAW,eAAe;AACpD;","names":[]}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var no_op_logbox_exports = {};
20
+ __export(no_op_logbox_exports, {
21
+ default: () => no_op_logbox_default,
22
+ dismissGlobalErrorOverlay: () => dismissGlobalErrorOverlay,
23
+ presentGlobalErrorOverlay: () => presentGlobalErrorOverlay,
24
+ setupLogBox: () => setupLogBox
25
+ });
26
+ module.exports = __toCommonJS(no_op_logbox_exports);
27
+ const LogBox = {
28
+ install() {
29
+ },
30
+ uninstall() {
31
+ },
32
+ ignoreLogs() {
33
+ },
34
+ ignoreAllLogs() {
35
+ },
36
+ clearAllLogs() {
37
+ },
38
+ addLog() {
39
+ },
40
+ addException() {
41
+ }
42
+ };
43
+ var no_op_logbox_default = LogBox;
44
+ const setupLogBox = () => {
45
+ };
46
+ const presentGlobalErrorOverlay = () => {
47
+ };
48
+ const dismissGlobalErrorOverlay = () => {
49
+ };
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ dismissGlobalErrorOverlay,
53
+ presentGlobalErrorOverlay,
54
+ setupLogBox
55
+ });
56
+ //# sourceMappingURL=no-op-logbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/stubs/no-op-logbox.ts"],"sourcesContent":["/**\n * No-op stub:在 web 端完全禁用 Expo LogBox UI(全屏 overlay 和 toast)。\n * 通过 Metro resolver 在构建层替换 @expo/log-box,消除开发环境的错误遮罩。\n */\n\nconst LogBox = {\n install() {},\n uninstall() {},\n ignoreLogs() {},\n ignoreAllLogs() {},\n clearAllLogs() {},\n addLog() {},\n addException() {},\n};\n\n// @expo/log-box 期望的具名导出\nexport default LogBox;\nexport const setupLogBox = () => {};\nexport const presentGlobalErrorOverlay = () => {};\nexport const dismissGlobalErrorOverlay = () => {};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,MAAM,SAAS;AAAA,EACb,UAAU;AAAA,EAAC;AAAA,EACX,YAAY;AAAA,EAAC;AAAA,EACb,aAAa;AAAA,EAAC;AAAA,EACd,gBAAgB;AAAA,EAAC;AAAA,EACjB,eAAe;AAAA,EAAC;AAAA,EAChB,SAAS;AAAA,EAAC;AAAA,EACV,eAAe;AAAA,EAAC;AAClB;AAGA,IAAO,uBAAQ;AACR,MAAM,cAAc,MAAM;AAAC;AAC3B,MAAM,4BAA4B,MAAM;AAAC;AACzC,MAAM,4BAA4B,MAAM;AAAC;","names":[]}
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var router_control_exports = {};
20
+ __export(router_control_exports, {
21
+ setupRouterControl: () => setupRouterControl
22
+ });
23
+ module.exports = __toCommonJS(router_control_exports);
24
+ function isRouterMessage(data) {
25
+ if (typeof data !== "object" || data === null) return false;
26
+ const { type } = data;
27
+ return type === "editor-location-update" || type === "editor-refresh";
28
+ }
29
+ function onMessage(e) {
30
+ if (!isRouterMessage(e.data)) return;
31
+ switch (e.data.type) {
32
+ case "editor-location-update": {
33
+ const { pageName } = e.data;
34
+ if (pageName) {
35
+ window.history.pushState({}, "", pageName);
36
+ window.dispatchEvent(new Event("popstate"));
37
+ }
38
+ break;
39
+ }
40
+ case "editor-refresh":
41
+ window.location.reload();
42
+ break;
43
+ }
44
+ }
45
+ function setupRouterControl() {
46
+ if (typeof document === "undefined" || typeof window === "undefined") return;
47
+ window.addEventListener("message", onMessage);
48
+ }
49
+ // Annotate the CommonJS export names for ESM import in node:
50
+ 0 && (module.exports = {
51
+ setupRouterControl
52
+ });
53
+ //# sourceMappingURL=router-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/stubs/router-control.ts"],"sourcesContent":["/// <reference lib=\"dom\" />\n/**\n * LGUI 路由控制器。\n *\n * 监听父窗口 postMessage,处理路由导航和页面刷新。\n *\n * 消息格式(父窗口 -> iframe):\n * window.postMessage({ type: 'editor-location-update', pageName }, '*') // 更新路由\n * window.postMessage({ type: 'editor-refresh' }, '*') // 刷新页面\n */\n\ntype RouterMessage = 'editor-location-update' | 'editor-refresh';\n\ninterface LocationData {\n pageName: string;\n}\n\nfunction isRouterMessage(data: unknown): data is { type: RouterMessage } {\n if (typeof data !== 'object' || data === null) return false;\n const { type } = data as { type?: unknown };\n return type === 'editor-location-update' || type === 'editor-refresh';\n}\n\nfunction onMessage(e: MessageEvent): void {\n if (!isRouterMessage(e.data)) return;\n\n switch (e.data.type) {\n case 'editor-location-update': {\n const { pageName } = e.data as unknown as LocationData;\n if (pageName) {\n window.history.pushState({}, '', pageName);\n window.dispatchEvent(new Event('popstate'));\n }\n break;\n }\n case 'editor-refresh':\n window.location.reload();\n break;\n }\n}\n\n/**\n * 初始化路由消息控制器。\n * 仅在 browser 环境下执行,React Native 安全。\n */\nexport function setupRouterControl(): void {\n // React Native 中 window 存在但 document/history/location 不完整,需同时检测\n if (typeof document === 'undefined' || typeof window === 'undefined') return;\n window.addEventListener('message', onMessage);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,SAAS,gBAAgB,MAAgD;AACvE,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,QAAM,EAAE,KAAK,IAAI;AACjB,SAAO,SAAS,4BAA4B,SAAS;AACvD;AAEA,SAAS,UAAU,GAAuB;AACxC,MAAI,CAAC,gBAAgB,EAAE,IAAI,EAAG;AAE9B,UAAQ,EAAE,KAAK,MAAM;AAAA,IACnB,KAAK,0BAA0B;AAC7B,YAAM,EAAE,SAAS,IAAI,EAAE;AACvB,UAAI,UAAU;AACZ,eAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,QAAQ;AACzC,eAAO,cAAc,IAAI,MAAM,UAAU,CAAC;AAAA,MAC5C;AACA;AAAA,IACF;AAAA,IACA,KAAK;AACH,aAAO,SAAS,OAAO;AACvB;AAAA,EACJ;AACF;AAMO,SAAS,qBAA2B;AAEzC,MAAI,OAAO,aAAa,eAAe,OAAO,WAAW,YAAa;AACtE,SAAO,iBAAiB,WAAW,SAAS;AAC9C;","names":[]}
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/stubs/sentry-react-native-stub.ts
26
+ var RealSentry = __toESM(require("@sentry/react-native"));
27
+
28
+ // src/capture.ts
29
+ var import_stacktrace_parser = require("stacktrace-parser");
30
+ var import_react_native = require("react-native");
31
+ var MetroSymbolicator = class _MetroSymbolicator {
32
+ constructor({ baseUrl } = {}) {
33
+ this.baseUrl = baseUrl ?? _MetroSymbolicator._getDefaultBaseUrl();
34
+ }
35
+ /**
36
+ * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
37
+ * web 平台使用相对路径;native 回退到 localhost:8081。
38
+ */
39
+ static _getDefaultBaseUrl() {
40
+ try {
41
+ const scriptURL = import_react_native.NativeModules?.SourceCode?.scriptURL;
42
+ if (scriptURL) {
43
+ const url = new URL(scriptURL);
44
+ return `${url.protocol}//${url.host}`;
45
+ }
46
+ } catch (_) {
47
+ }
48
+ return import_react_native.Platform.OS === "web" ? "" : "http://localhost:8081";
49
+ }
50
+ /**
51
+ * 解析 Error.stack 字符串为 Metro 所需的帧数组。
52
+ *
53
+ * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
54
+ * V8/Node.js/Hermes(新):at methodName (url:line:col)
55
+ * Gecko/Firefox/Hermes(旧):methodName@url:line:col
56
+ * JavaScriptCore(iOS):methodName@url:line:col
57
+ *
58
+ * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
59
+ */
60
+ parseErrorStack(stack) {
61
+ return (0, import_stacktrace_parser.parse)(stack).filter(
62
+ (f) => f.file != null && f.lineNumber != null
63
+ ).map((f) => ({
64
+ file: f.file,
65
+ lineNumber: f.lineNumber,
66
+ column: f.column != null ? f.column - 1 : 0,
67
+ methodName: f.methodName === "<unknown>" ? "(anonymous)" : f.methodName
68
+ }));
69
+ }
70
+ /**
71
+ * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
72
+ */
73
+ async symbolicate(stack) {
74
+ if (stack.length === 0) return null;
75
+ const endpoint = `${this.baseUrl}/symbolicate`;
76
+ try {
77
+ const res = await fetch(endpoint, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ stack })
81
+ });
82
+ if (!res.ok) {
83
+ console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);
84
+ return null;
85
+ }
86
+ const data = await res.json();
87
+ return data.stack ?? null;
88
+ } catch (e) {
89
+ console.warn("[SentryCapture] symbolication failed:", e?.message ?? e);
90
+ return null;
91
+ }
92
+ }
93
+ };
94
+ var SentryCapture = class {
95
+ constructor({ onError, onNetwork, symbolicator } = {}) {
96
+ this.onError = onError;
97
+ this.onNetwork = onNetwork;
98
+ this.symbolicator = symbolicator ?? new MetroSymbolicator();
99
+ this.beforeSend = this.beforeSend.bind(this);
100
+ this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);
101
+ }
102
+ /**
103
+ * Sentry beforeSend hook。
104
+ * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
105
+ * 符号化完成后触发 onError 回调,原样返回 event。
106
+ */
107
+ async beforeSend(event, hint) {
108
+ globalThis.__SENTRY_LAST_RAW__ = { event, hint };
109
+ const message = event.exception?.values?.[0]?.value ?? event.message ?? "(unknown)";
110
+ let symbolicatedFrames = null;
111
+ const originalException = hint?.originalException;
112
+ const rawStack = originalException instanceof Error && typeof originalException.stack === "string" ? originalException.stack : null;
113
+ if (rawStack !== null) {
114
+ const frames = this.symbolicator.parseErrorStack(rawStack);
115
+ if (frames.length > 0) {
116
+ symbolicatedFrames = await this.symbolicator.symbolicate(frames);
117
+ }
118
+ }
119
+ if (symbolicatedFrames === null) {
120
+ const sentryFrames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
121
+ const mapped = sentryFrames.filter(
122
+ (f) => f.filename != null && f.lineno != null
123
+ ).map(
124
+ (f) => ({
125
+ file: f.filename,
126
+ lineNumber: f.lineno,
127
+ column: f.colno ?? 0,
128
+ methodName: f.function ?? "(anonymous)"
129
+ })
130
+ );
131
+ if (mapped.length > 0) {
132
+ symbolicatedFrames = await this.symbolicator.symbolicate(mapped);
133
+ }
134
+ }
135
+ const componentStack = event.contexts?.react?.componentStack ?? null;
136
+ const mechanismType = event.exception?.values?.[0]?.mechanism?.type ?? null;
137
+ this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });
138
+ return event;
139
+ }
140
+ /**
141
+ * Sentry beforeBreadcrumb hook。
142
+ * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
143
+ */
144
+ beforeBreadcrumb(breadcrumb, _hint) {
145
+ if (breadcrumb.type === "http") {
146
+ const data = breadcrumb.data;
147
+ this.onNetwork?.({
148
+ method: data?.method ?? null,
149
+ url: data?.url ?? null,
150
+ statusCode: data?.status_code ?? null
151
+ });
152
+ }
153
+ return breadcrumb;
154
+ }
155
+ };
156
+
157
+ // src/stubs/sentry-react-native-stub.ts
158
+ function getParentOrigin() {
159
+ try {
160
+ const w = globalThis.window;
161
+ if (!w) return "*";
162
+ return new URLSearchParams(w.location.search).get("parentOrigin") || "*";
163
+ } catch {
164
+ return "*";
165
+ }
166
+ }
167
+ var OVERRIDE_DSN = process.env["SENTRY_OVERRIDE_DSN"] ?? "https://stubPublicKey@o0.ingest.sentry.io/0";
168
+ var _capture = new SentryCapture({
169
+ onError(info) {
170
+ console.warn("[SentryStub] \u6355\u83B7\u5230\u9519\u8BEF\uFF1A", info.message);
171
+ if (info.symbolicatedFrames?.length) {
172
+ const top = info.symbolicatedFrames[0];
173
+ console.warn(` at ${top.methodName} (${top.file}:${top.lineNumber}:${top.column})`);
174
+ }
175
+ const w = globalThis.window;
176
+ if (w !== void 0 && w !== w.parent) {
177
+ w.parent.postMessage(
178
+ {
179
+ type: "GLOBAL_ERROR",
180
+ error: {
181
+ message: info.message,
182
+ symbolicatedFrames: info.symbolicatedFrames,
183
+ componentStack: info.componentStack,
184
+ mechanismType: info.mechanismType
185
+ }
186
+ },
187
+ getParentOrigin()
188
+ );
189
+ }
190
+ }
191
+ });
192
+ var stub = {
193
+ ...RealSentry,
194
+ /**
195
+ * 拦截 init():替换 DSN 并注入内置捕获器。
196
+ * 调用方传入的 beforeSend / beforeBreadcrumb 在内置捕获器之后串联执行。
197
+ */
198
+ init: ((options) => {
199
+ const upstreamBeforeSend = options?.beforeSend;
200
+ const upstreamBeforeBreadcrumb = options?.beforeBreadcrumb;
201
+ const initOptions = {
202
+ ...options,
203
+ dsn: OVERRIDE_DSN,
204
+ async beforeSend(event, hint) {
205
+ const processed = await _capture.beforeSend(event, hint);
206
+ return upstreamBeforeSend ? await upstreamBeforeSend(processed, hint) : processed;
207
+ },
208
+ beforeBreadcrumb(breadcrumb, hint) {
209
+ const processed = _capture.beforeBreadcrumb(breadcrumb, hint);
210
+ return upstreamBeforeBreadcrumb ? upstreamBeforeBreadcrumb(processed, hint) : processed;
211
+ }
212
+ };
213
+ return RealSentry.init(initOptions);
214
+ })
215
+ };
216
+ module.exports = stub;
217
+ //# sourceMappingURL=sentry-react-native-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/stubs/sentry-react-native-stub.ts","../../src/capture.ts"],"sourcesContent":["/**\n * miaoda-expo-devkit — @sentry/react-native 模块替换 stub\n *\n * 在 Metro 构建层替换 @sentry/react-native,拦截 init() 以:\n * 1. 将用户传入的 DSN 替换为无害的覆盖值,防止开发环境事件上报到真实服务器\n * 2. 自动注入内置 SentryCapture,监听并符号化 Sentry 错误事件\n * 3. 串联调用方传入的 beforeSend / beforeBreadcrumb(内置捕获器先执行)\n *\n * 通过 SENTRY_OVERRIDE_DSN 环境变量可自定义覆盖 DSN(如指向本地 relay)。\n * 此文件由 withDevStubs()(metro.ts)在 Metro 层自动注入,无需手动 import。\n */\n\nimport * as RealSentry from '@sentry/react-native';\nimport { SentryCapture } from '../capture';\n\n/** 仅声明 postMessage 场景所需的最小 window 接口,避免依赖 DOM lib。 */\ninterface BrowserWindow {\n parent: BrowserWindow;\n location: { search: string };\n postMessage(message: unknown, targetOrigin: string): void;\n}\n\n/** 从 URL 参数读取父窗口域名,与 vite-sandbox injected script 逻辑对齐。 */\nfunction getParentOrigin(): string {\n try {\n const w = (globalThis as { window?: BrowserWindow }).window;\n if (!w) return '*';\n return new URLSearchParams(w.location.search).get('parentOrigin') || '*';\n } catch {\n return '*';\n }\n}\n\n/**\n * 覆盖 DSN。必须为合法格式(project ID 为数字),否则 @sentry/react-native 校验失败会跳过整个事件管道。\n */\nconst OVERRIDE_DSN: string =\n process.env['SENTRY_OVERRIDE_DSN'] ?? 'https://stubPublicKey@o0.ingest.sentry.io/0';\n\n// 通过工具类型从 RealSentry.init 参数推导,避免直接 import @sentry/core\ntype InitOptions = NonNullable<Parameters<typeof RealSentry.init>[0]>;\ntype SentryEvent = Parameters<NonNullable<InitOptions['beforeSend']>>[0];\ntype SentryEventHint = Parameters<NonNullable<InitOptions['beforeSend']>>[1];\ntype SentryBreadcrumb = Parameters<NonNullable<InitOptions['beforeBreadcrumb']>>[0];\ntype SentryBreadcrumbHint = Parameters<NonNullable<InitOptions['beforeBreadcrumb']>>[1];\n\n/**\n * 内置捕获器。在 init() 中自动注入到 beforeSend / beforeBreadcrumb。\n * 默认将错误摘要及栈顶帧输出到 console.warn。\n */\nconst _capture = new SentryCapture({\n onError(info) {\n console.warn('[SentryStub] 捕获到错误:', info.message);\n if (info.symbolicatedFrames?.length) {\n const top = info.symbolicatedFrames[0];\n console.warn(` at ${top.methodName} (${top.file}:${top.lineNumber}:${top.column})`);\n }\n\n // 向父窗口抛出 GLOBAL_ERROR 事件,与 vite-sandbox EditorOutgoingMessageType 对齐\n const w = (globalThis as { window?: BrowserWindow }).window;\n if (w !== undefined && w !== w.parent) {\n w.parent.postMessage(\n {\n type: 'GLOBAL_ERROR',\n error: {\n message: info.message,\n symbolicatedFrames: info.symbolicatedFrames,\n componentStack: info.componentStack,\n mechanismType: info.mechanismType,\n },\n },\n getParentOrigin(),\n );\n }\n },\n});\n\n// 类型声明为 typeof RealSentry,避免 declaration 阶段展开 @sentry/react-native 内部私有类型\nconst stub: typeof RealSentry = {\n ...RealSentry,\n\n /**\n * 拦截 init():替换 DSN 并注入内置捕获器。\n * 调用方传入的 beforeSend / beforeBreadcrumb 在内置捕获器之后串联执行。\n */\n init: ((options: Parameters<typeof RealSentry.init>[0]) => {\n const upstreamBeforeSend = options?.beforeSend;\n const upstreamBeforeBreadcrumb = options?.beforeBreadcrumb;\n\n const initOptions: InitOptions = {\n ...options,\n dsn: OVERRIDE_DSN,\n\n async beforeSend(event: SentryEvent, hint: SentryEventHint): Promise<SentryEvent | null> {\n const processed = await _capture.beforeSend(event, hint);\n return upstreamBeforeSend ? await upstreamBeforeSend(processed, hint) : processed;\n },\n\n beforeBreadcrumb(\n breadcrumb: SentryBreadcrumb,\n hint?: SentryBreadcrumbHint,\n ): SentryBreadcrumb | null {\n const processed = _capture.beforeBreadcrumb(breadcrumb, hint);\n return upstreamBeforeBreadcrumb ? upstreamBeforeBreadcrumb(processed, hint) : processed;\n },\n };\n\n return RealSentry.init(initOptions);\n }) as typeof RealSentry.init,\n};\n\nexport = stub;\n","/**\n * Sentry Capture 模块\n *\n * 提供 Metro 符号化(MetroSymbolicator)和 Sentry 事件捕获(SentryCapture)。\n */\n\nimport type { Breadcrumb, BreadcrumbHint, ErrorEvent, EventHint, StackFrame } from '@sentry/core';\nimport { parse as parseStackTrace } from 'stacktrace-parser';\nimport { NativeModules, Platform } from 'react-native';\n\n// Breadcrumb.data 为 Record<string, unknown>,此处仅声明 HTTP 场景实际用到的字段\ninterface FetchBreadcrumbData {\n method?: string;\n url?: string;\n status_code?: number;\n}\n\n// ── 类型定义 ───────────────────────────────────────────────────────────────────\n\n/** Metro /symbolicate 接口的帧格式(请求与响应共用)。 */\nexport interface MetroFrame {\n file: string;\n lineNumber: number;\n column: number;\n methodName: string;\n}\n\n/** SentryCapture.onError 回调参数 */\nexport interface ErrorCaptureInfo {\n message: string;\n symbolicatedFrames: MetroFrame[] | null;\n componentStack: string | null;\n /** Sentry Mechanism.type,如 'onerror' / 'generic' / 'auto.function.react.error_boundary' */\n mechanismType: string | null;\n}\n\n/** SentryCapture.onNetwork 回调参数 */\nexport interface NetworkCaptureInfo {\n method: string | null;\n url: string | null;\n statusCode: number | null;\n}\n\nexport interface SentryCaptureOptions {\n onError?: (info: ErrorCaptureInfo) => void;\n onNetwork?: (info: NetworkCaptureInfo) => void;\n /** 可注入 mock 实例以在单元测试中隔离网络依赖 */\n symbolicator?: MetroSymbolicator;\n}\n\n// ── MetroSymbolicator ──────────────────────────────────────────────────────────\n\n/**\n * Metro 符号化器。\n * 将 bundle 行列号还原为源码位置(通过 Metro /symbolicate 接口)。\n *\n * @example\n * const sym = new MetroSymbolicator();\n *\n * @example\n * // 单测:注入 baseUrl 并 mock fetch\n * const sym = new MetroSymbolicator({ baseUrl: 'http://localhost:8081' });\n * global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ stack: [...] }) });\n */\nexport class MetroSymbolicator {\n readonly baseUrl: string;\n\n constructor({ baseUrl }: { baseUrl?: string } = {}) {\n this.baseUrl = baseUrl ?? MetroSymbolicator._getDefaultBaseUrl();\n }\n\n /**\n * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。\n * web 平台使用相对路径;native 回退到 localhost:8081。\n */\n private static _getDefaultBaseUrl(): string {\n try {\n const scriptURL = NativeModules?.SourceCode?.scriptURL as string | undefined;\n if (scriptURL) {\n const url = new URL(scriptURL);\n return `${url.protocol}//${url.host}`;\n }\n } catch (_) {}\n return Platform.OS === 'web' ? '' : 'http://localhost:8081';\n }\n\n /**\n * 解析 Error.stack 字符串为 Metro 所需的帧数组。\n *\n * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:\n * V8/Node.js/Hermes(新):at methodName (url:line:col)\n * Gecko/Firefox/Hermes(旧):methodName@url:line:col\n * JavaScriptCore(iOS):methodName@url:line:col\n *\n * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。\n */\n parseErrorStack(stack: string): MetroFrame[] {\n return parseStackTrace(stack)\n .filter(\n (f): f is typeof f & { file: string; lineNumber: number } =>\n f.file != null && f.lineNumber != null,\n )\n .map(f => ({\n file: f.file,\n lineNumber: f.lineNumber,\n column: f.column != null ? f.column - 1 : 0,\n methodName: f.methodName === '<unknown>' ? '(anonymous)' : f.methodName,\n }));\n }\n\n /**\n * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。\n */\n async symbolicate(stack: MetroFrame[]): Promise<MetroFrame[] | null> {\n if (stack.length === 0) return null;\n const endpoint = `${this.baseUrl}/symbolicate`;\n try {\n const res = await fetch(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ stack }),\n });\n if (!res.ok) {\n console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);\n return null;\n }\n const data = (await res.json()) as { stack?: MetroFrame[] };\n return data.stack ?? null;\n } catch (e) {\n console.warn('[SentryCapture] symbolication failed:', (e as Error)?.message ?? e);\n return null;\n }\n }\n}\n\n// ── SentryCapture ─────────────────────────────────────────────────────────────\n\n/**\n * Sentry 事件捕获器。\n * 封装 beforeSend / beforeBreadcrumb 逻辑,通过注入的回调将事件数据交给调用方处理。\n *\n * @example\n * const capture = new SentryCapture({\n * onError(info) { console.log(info.message, info.symbolicatedFrames); },\n * onNetwork(info) { console.log(info.method, info.url, info.statusCode); },\n * });\n * Sentry.init({ dsn: '...', beforeSend: capture.beforeSend, beforeBreadcrumb: capture.beforeBreadcrumb });\n *\n * @example\n * // 单测:注入 mock symbolicator\n * const mockSym = { parseErrorStack: jest.fn(() => []), symbolicate: jest.fn(async () => null) };\n * const onError = jest.fn();\n * const capture = new SentryCapture({ onError, symbolicator: mockSym });\n * await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });\n * expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'oops' }));\n */\nexport class SentryCapture {\n private readonly onError: SentryCaptureOptions['onError'];\n private readonly onNetwork: SentryCaptureOptions['onNetwork'];\n private readonly symbolicator: MetroSymbolicator;\n\n constructor({ onError, onNetwork, symbolicator }: SentryCaptureOptions = {}) {\n this.onError = onError;\n this.onNetwork = onNetwork;\n this.symbolicator = symbolicator ?? new MetroSymbolicator();\n\n // bind 保证方法作为回调传入 Sentry.init() 时 this 不丢失\n this.beforeSend = this.beforeSend.bind(this);\n this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);\n }\n\n /**\n * Sentry beforeSend hook。\n * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。\n * 符号化完成后触发 onError 回调,原样返回 event。\n */\n async beforeSend(event: ErrorEvent, hint: EventHint): Promise<ErrorEvent> {\n globalThis.__SENTRY_LAST_RAW__ = { event, hint };\n\n const message: string =\n event.exception?.values?.[0]?.value ?? event.message ?? '(unknown)';\n\n let symbolicatedFrames: MetroFrame[] | null = null;\n\n const originalException: unknown = hint?.originalException;\n const rawStack: string | null =\n originalException instanceof Error && typeof originalException.stack === 'string'\n ? originalException.stack\n : null;\n\n if (rawStack !== null) {\n const frames: MetroFrame[] = this.symbolicator.parseErrorStack(rawStack);\n if (frames.length > 0) {\n symbolicatedFrames = await this.symbolicator.symbolicate(frames);\n }\n }\n\n // 回退:使用 Sentry 事件帧\n if (symbolicatedFrames === null) {\n const sentryFrames: StackFrame[] =\n event.exception?.values?.[0]?.stacktrace?.frames ?? [];\n const mapped: MetroFrame[] = sentryFrames\n .filter(\n (f): f is StackFrame & Required<Pick<StackFrame, 'filename' | 'lineno'>> =>\n f.filename != null && f.lineno != null,\n )\n .map(\n (f): MetroFrame => ({\n file: f.filename,\n lineNumber: f.lineno,\n column: f.colno ?? 0,\n methodName: f.function ?? '(anonymous)',\n }),\n );\n if (mapped.length > 0) {\n symbolicatedFrames = await this.symbolicator.symbolicate(mapped);\n }\n }\n\n const componentStack: string | null =\n (event.contexts?.react?.componentStack as string | undefined) ?? null;\n\n const mechanismType: string | null =\n event.exception?.values?.[0]?.mechanism?.type ?? null;\n\n this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });\n\n return event;\n }\n\n /**\n * Sentry beforeBreadcrumb hook。\n * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。\n */\n beforeBreadcrumb(breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb {\n if (breadcrumb.type === 'http') {\n const data = breadcrumb.data as FetchBreadcrumbData | undefined;\n this.onNetwork?.({\n method: data?.method ?? null,\n url: data?.url ?? null,\n statusCode: data?.status_code ?? null,\n });\n }\n return breadcrumb;\n }\n}\n"],"mappings":"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","names":["parseStackTrace"]}