miaoda-expo-devkit 0.1.1-beta.112 → 0.1.1-beta.113
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/README.md +28 -111
- package/dist/index.d.mts +1 -110
- package/dist/index.d.ts +1 -110
- package/dist/index.js +0 -159
- package/dist/index.mjs +0 -132
- package/dist/stubs/entry-inject.js +0 -2
- package/dist/stubs/sentry-react-native-stub.js +15 -180
- package/package.json +2 -3
- package/pnpm-patches/@expo__cli@55.0.16-55.0.22.patch +3 -90
- package/dist/stubs/preview-control.js +0 -60
package/README.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Expo / React Native 开发环境工具集,通过 Metro 构建层注入以下能力:
|
|
4
4
|
|
|
5
|
-
- **Sentry
|
|
6
|
-
- **错误 / 网络捕获** — 内置 `SentryCapture`,自动调用 Metro `/symbolicate` 还原源码位置并输出到 `console.warn`
|
|
5
|
+
- **Sentry 初始化守卫** — Native 透传真实初始化;独立 Expo Web 使用安全 fallback;sandbox Web 由前置 polyfill 唯一初始化
|
|
7
6
|
- **Bundle 首部注入** — 在 expo-router 初始化之前执行自定义脚本
|
|
8
7
|
- **HMR postMessage 控制** — 通过 `window.postMessage` 在运行时启动或停止 Fast Refresh
|
|
9
8
|
- **LogBox 屏蔽** — web 平台禁用 Expo 全屏错误遮罩
|
|
@@ -60,7 +59,7 @@ module.exports = withExpoCalendarStub(withExpoMediaLibraryStub(withEntryInjectio
|
|
|
60
59
|
|
|
61
60
|
### Sentry 初始化
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
应用可照常调用 `Sentry.init`:
|
|
64
63
|
|
|
65
64
|
```ts
|
|
66
65
|
// app/_layout.tsx
|
|
@@ -68,34 +67,17 @@ import * as Sentry from '@sentry/react-native';
|
|
|
68
67
|
|
|
69
68
|
Sentry.init({
|
|
70
69
|
dsn: 'https://your-key@sentry.io/your-project',
|
|
71
|
-
// stub 自动拦截 beforeSend / beforeBreadcrumb,错误摘要输出到 console.warn
|
|
72
70
|
});
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
stub 的行为按平台区分:
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
- iOS/Android:原样调用真实 `@sentry/react-native` init,应用参数不变;
|
|
76
|
+
- 独立 Expo Web:用安全 fallback DSN 初始化,避免开发事件进入真实项目;
|
|
77
|
+
- vite-sandbox Expo Web:sandbox 生成的 Metro polyfill 已经把应用实际依赖的 `@sentry/browser` 与统一观测运行时打包,并先于应用完成唯一一次 init;stub 检测版本字符串在场标记后吞掉应用 init;
|
|
78
|
+
- 版本不一致:只输出 `console.error`,不向编辑器宿主发送消息。
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
import * as Sentry from '@sentry/react-native';
|
|
81
|
-
import { SentryCapture } from 'miaoda-expo-devkit';
|
|
82
|
-
import type { ErrorCaptureInfo, NetworkCaptureInfo } from 'miaoda-expo-devkit';
|
|
83
|
-
|
|
84
|
-
const capture = new SentryCapture({
|
|
85
|
-
onError(info: ErrorCaptureInfo) {
|
|
86
|
-
console.log('Error at:', info.symbolicatedFrames?.[0]);
|
|
87
|
-
},
|
|
88
|
-
onNetwork(info: NetworkCaptureInfo) {
|
|
89
|
-
console.log('Network:', info.method, info.url, info.statusCode);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
Sentry.init({
|
|
94
|
-
dsn: 'https://your-key@sentry.io/your-project',
|
|
95
|
-
beforeSend: capture.beforeSend,
|
|
96
|
-
beforeBreadcrumb: capture.beforeBreadcrumb,
|
|
97
|
-
});
|
|
98
|
-
```
|
|
80
|
+
fetch/XHR 取数、网络与 console 事件、原始数据长度限制、Supabase 白名单、`GLOBAL_ERROR` 和 Metro `/symbolicate` 均由 vite-sandbox 的同一份运行时维护。devkit 不复制这些规则,也不再包含 capture 或 runtime bridge。
|
|
99
81
|
|
|
100
82
|
### HMR postMessage 控制
|
|
101
83
|
|
|
@@ -151,75 +133,6 @@ window.__DEVKIT_HMR_ENABLED__ // true | false
|
|
|
151
133
|
|
|
152
134
|
---
|
|
153
135
|
|
|
154
|
-
### `new SentryCapture(options)`
|
|
155
|
-
|
|
156
|
-
从 `miaoda-expo-devkit` 引入。
|
|
157
|
-
|
|
158
|
-
用于自定义捕获行为。stub 内置了一个默认实例(输出到 `console.warn`),此类用于叠加自定义处理逻辑。
|
|
159
|
-
|
|
160
|
-
| 选项 | 类型 | 说明 |
|
|
161
|
-
|---|---|---|
|
|
162
|
-
| `onError` | `(info: ErrorCaptureInfo) => void` | 捕获到错误时触发,携带符号化后的调用栈 |
|
|
163
|
-
| `onNetwork` | `(info: NetworkCaptureInfo) => void` | 捕获到 HTTP 请求时触发 |
|
|
164
|
-
| `symbolicator` | `MetroSymbolicator` | 可选,单测时注入 mock 以隔离网络依赖 |
|
|
165
|
-
|
|
166
|
-
构造完成后,将绑定方法传给 `Sentry.init`:
|
|
167
|
-
|
|
168
|
-
```ts
|
|
169
|
-
Sentry.init({
|
|
170
|
-
beforeSend: capture.beforeSend,
|
|
171
|
-
beforeBreadcrumb: capture.beforeBreadcrumb,
|
|
172
|
-
});
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
#### `ErrorCaptureInfo`
|
|
176
|
-
|
|
177
|
-
| 字段 | 类型 | 说明 |
|
|
178
|
-
|---|---|---|
|
|
179
|
-
| `message` | `string` | 错误信息 |
|
|
180
|
-
| `symbolicatedFrames` | `MetroFrame[] \| null` | 经 Metro 符号化后还原到源码位置的调用栈帧 |
|
|
181
|
-
| `componentStack` | `string \| null` | React 组件调用栈(ErrorBoundary 捕获的错误才有值) |
|
|
182
|
-
| `mechanismType` | `string \| null` | Sentry 捕获方式:`'onerror'`、`'generic'`、`'auto.function.react.error_boundary'` 等 |
|
|
183
|
-
|
|
184
|
-
#### `NetworkCaptureInfo`
|
|
185
|
-
|
|
186
|
-
| 字段 | 类型 | 说明 |
|
|
187
|
-
|---|---|---|
|
|
188
|
-
| `method` | `string \| null` | HTTP 方法 |
|
|
189
|
-
| `url` | `string \| null` | 请求 URL |
|
|
190
|
-
| `statusCode` | `number \| null` | HTTP 状态码(连接级失败为 `null`) |
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
### `new MetroSymbolicator(options?)`
|
|
195
|
-
|
|
196
|
-
从 `miaoda-expo-devkit` 引入。
|
|
197
|
-
|
|
198
|
-
通过 Metro `/symbolicate` 接口将 bundle 行列号还原为源码位置。`SentryCapture` 内部已默认使用,主要用途是单测时注入 mock。
|
|
199
|
-
|
|
200
|
-
| 选项 | 类型 | 说明 |
|
|
201
|
-
|---|---|---|
|
|
202
|
-
| `baseUrl` | `string` | Metro server 地址。不传则从 `NativeModules.SourceCode.scriptURL` 自动推断,fallback 到 `localhost:8081`。 |
|
|
203
|
-
|
|
204
|
-
**单测示例:**
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
const mockSym = {
|
|
208
|
-
parseErrorStack: jest.fn(() => []),
|
|
209
|
-
symbolicate: jest.fn(async () => null),
|
|
210
|
-
};
|
|
211
|
-
const onError = jest.fn();
|
|
212
|
-
const capture = new SentryCapture({ onError, symbolicator: mockSym });
|
|
213
|
-
|
|
214
|
-
await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
|
|
215
|
-
|
|
216
|
-
expect(onError).toHaveBeenCalledWith(
|
|
217
|
-
expect.objectContaining({ message: 'oops' }),
|
|
218
|
-
);
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
---
|
|
222
|
-
|
|
223
136
|
## 全局标记
|
|
224
137
|
|
|
225
138
|
注入脚本在 bundle 执行时(早于任何 React 代码)写入以下全局变量,可用于 E2E 测试断言:
|
|
@@ -233,7 +146,7 @@ expect(onError).toHaveBeenCalledWith(
|
|
|
233
146
|
|
|
234
147
|
| 变量 | 默认值 | 说明 |
|
|
235
148
|
|---|---|---|
|
|
236
|
-
| `
|
|
149
|
+
| `INJECT_SENTRY_DSN` | 安全 stub DSN | vite-sandbox 服务端已有变量;sandbox 会自动编入生成 runtime,应用开发者无需配置 |
|
|
237
150
|
| `METRO_CACHE_DIR` | `projectRoot/.metro-cache` | Metro transform 缓存目录绝对路径,优先级最高,适合容器/CI 挂载外部持久目录 |
|
|
238
151
|
| `METRO_TRANSFORM_LOG` | _(未设置时不记录)_ | 构建日志输出文件的绝对路径(JSONL 格式),仅开发模式(`__DEV__`)下生效 |
|
|
239
152
|
| `EXPO_AI_ERROR_COLLECTOR` | _(未设置时关闭)_ | 设为 `1` 后由 DevKit 管理的 Expo CLI patch 采集当前 Metro 编译状态 |
|
|
@@ -252,10 +165,8 @@ EXPO_AI_ERROR_COLLECTOR=1 npx expo start
|
|
|
252
165
|
可能清理这个目录;需要持久化时请设置 `EXPO_AI_ERROR_FILE` 指向项目外部路径。
|
|
253
166
|
|
|
254
167
|
状态文件只表示“当前编译”:`bundle_build_started` 会立即清除上一次错误,
|
|
255
|
-
`bundling_error`
|
|
256
|
-
|
|
257
|
-
`hmr_update_done` 状态,确保源码修复后通过 Fast Refresh 恢复时旧错误同步清除。
|
|
258
|
-
AI 进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebSocket 客户端。
|
|
168
|
+
`bundling_error` 写入结构化错误,下一次编译成功或失败都会替换旧状态。AI
|
|
169
|
+
进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebSocket 客户端。
|
|
259
170
|
|
|
260
171
|
返回对象包含 `status`(`compiling`、`failed`、`succeeded`)和 `error`。失败时
|
|
261
172
|
`error` 至少包含 `type`、`message`、`filename`、`lineNumber`、`column`、
|
|
@@ -265,6 +176,14 @@ AI 进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebS
|
|
|
265
176
|
该范围以外的 CLI 版本不会静默套用补丁;升级时需要先检查发布产物,确认兼容后再调整
|
|
266
177
|
`pnpmfile.cjs` 中的范围和对应测试。
|
|
267
178
|
|
|
179
|
+
以下变量只服务于 sandbox 专项 E2E,不是应用或线上部署配置:
|
|
180
|
+
|
|
181
|
+
| 变量 | 默认值 | 设置方与用途 |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `EXPO_OVERRIDE_METRO_CONFIG` | 无 | 线上由 vite-sandbox 自动设置;专项 E2E 由准备流程指向生成的 `node_modules/.cache/vite-sandbox/metro.config.cjs` |
|
|
184
|
+
| `EXPO_E2E_PORT` | `3001` | 仅在 Playwright 端口冲突时由本地或 CI 覆盖 |
|
|
185
|
+
| `EXPO_E2E_SANDBOX_PROJECT_ID` | `0` | 仅用于断言 Sentry envelope 使用 sandbox DSN 的 project id |
|
|
186
|
+
|
|
268
187
|
## 工作原理
|
|
269
188
|
|
|
270
189
|
```
|
|
@@ -362,18 +281,16 @@ setupHMRMessageControl()
|
|
|
362
281
|
|
|
363
282
|
sentry-react-native-stub.js
|
|
364
283
|
└─ init(options)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
│ └─ onError({ message, frames, ... }) → console.warn(默认)
|
|
369
|
-
└─ options.beforeSend?(event, hint) ← 调用方自定义(可选,串联执行)
|
|
284
|
+
├─ Native: RealSentry.init(options)
|
|
285
|
+
├─ 独立 Web: RealSentry.init({...options, dsn: FALLBACK_DSN})
|
|
286
|
+
└─ sandbox Web: 校验版本标记后返回,不重复初始化
|
|
370
287
|
```
|
|
371
288
|
|
|
372
289
|
## Package Exports
|
|
373
290
|
|
|
374
291
|
| 子路径 | 文件 | 内容 |
|
|
375
292
|
|---|---|---|
|
|
376
|
-
| `.` | `dist/index.js` |
|
|
293
|
+
| `.` | `dist/index.js` | 主入口(当前不导出运行时 API) |
|
|
377
294
|
| `./metro` | `dist/metro.js` | `withDevkit`、`withDevStubs`、`withEntryInjection`、`withPersistentCache`、`withTransformLogger`、`withExpoNotificationsStub`、`withExpoMediaLibraryStub`、`withExpoCalendarStub` 等全部 Metro wrapper |
|
|
378
295
|
| `./babel-plugin-jsx-source` | `dist/babel/plugin-jsx-source.js` | Babel 插件:为 JSX 注入 source 信息 |
|
|
379
296
|
| `./babel-preset` | `dist/babel/preset.js` | Babel Preset:集成 jsx-source 和 lucide 插件 |
|
|
@@ -562,13 +479,13 @@ window.postMessage({ type: 'editor-refresh' }, '*') //
|
|
|
562
479
|
`@sentry/react-native` 模块替换 stub,由 `withDevStubs()` 在 Metro 层自动注入。
|
|
563
480
|
|
|
564
481
|
**功能:**
|
|
565
|
-
-
|
|
566
|
-
-
|
|
567
|
-
-
|
|
568
|
-
-
|
|
482
|
+
- Native 原样透传应用的 Sentry init
|
|
483
|
+
- 独立 Web 使用安全 fallback DSN
|
|
484
|
+
- sandbox Web 校验运行时版本标记并阻止重复 init
|
|
485
|
+
- 版本不一致时只向 console 输出诊断信息,不通知编辑器宿主
|
|
569
486
|
|
|
570
487
|
**环境变量:**
|
|
571
|
-
- `
|
|
488
|
+
- 应用开发者不需要新增环境变量;`INJECT_SENTRY_DSN` 由 vite-sandbox 启动链路自动处理
|
|
572
489
|
|
|
573
490
|
---
|
|
574
491
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,111 +1,2 @@
|
|
|
1
|
-
import { ErrorEvent, EventHint, Breadcrumb, BreadcrumbHint } from '@sentry/core';
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
* Sentry Capture 模块
|
|
5
|
-
*
|
|
6
|
-
* 提供 Metro 符号化(MetroSymbolicator)和 Sentry 事件捕获(SentryCapture)。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Metro /symbolicate 接口的帧格式(请求与响应共用)。 */
|
|
10
|
-
interface MetroFrame {
|
|
11
|
-
file: string;
|
|
12
|
-
lineNumber: number;
|
|
13
|
-
column: number;
|
|
14
|
-
methodName: string;
|
|
15
|
-
}
|
|
16
|
-
/** SentryCapture.onError 回调参数 */
|
|
17
|
-
interface ErrorCaptureInfo {
|
|
18
|
-
message: string;
|
|
19
|
-
symbolicatedFrames: MetroFrame[] | null;
|
|
20
|
-
componentStack: string | null;
|
|
21
|
-
/** Sentry Mechanism.type,如 'onerror' / 'generic' / 'auto.function.react.error_boundary' */
|
|
22
|
-
mechanismType: string | null;
|
|
23
|
-
}
|
|
24
|
-
/** SentryCapture.onNetwork 回调参数 */
|
|
25
|
-
interface NetworkCaptureInfo {
|
|
26
|
-
method: string | null;
|
|
27
|
-
url: string | null;
|
|
28
|
-
statusCode: number | null;
|
|
29
|
-
}
|
|
30
|
-
interface SentryCaptureOptions {
|
|
31
|
-
onError?: (info: ErrorCaptureInfo) => void;
|
|
32
|
-
onNetwork?: (info: NetworkCaptureInfo) => void;
|
|
33
|
-
/** 可注入 mock 实例以在单元测试中隔离网络依赖 */
|
|
34
|
-
symbolicator?: MetroSymbolicator;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Metro 符号化器。
|
|
38
|
-
* 将 bundle 行列号还原为源码位置(通过 Metro /symbolicate 接口)。
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* const sym = new MetroSymbolicator();
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* // 单测:注入 baseUrl 并 mock fetch
|
|
45
|
-
* const sym = new MetroSymbolicator({ baseUrl: 'http://localhost:8081' });
|
|
46
|
-
* global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ stack: [...] }) });
|
|
47
|
-
*/
|
|
48
|
-
declare class MetroSymbolicator {
|
|
49
|
-
readonly baseUrl: string;
|
|
50
|
-
constructor({ baseUrl }?: {
|
|
51
|
-
baseUrl?: string;
|
|
52
|
-
});
|
|
53
|
-
/**
|
|
54
|
-
* 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
|
|
55
|
-
* web 平台使用相对路径;native 回退到 localhost:8081。
|
|
56
|
-
*/
|
|
57
|
-
private static _getDefaultBaseUrl;
|
|
58
|
-
/**
|
|
59
|
-
* 解析 Error.stack 字符串为 Metro 所需的帧数组。
|
|
60
|
-
*
|
|
61
|
-
* 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
|
|
62
|
-
* V8/Node.js/Hermes(新):at methodName (url:line:col)
|
|
63
|
-
* Gecko/Firefox/Hermes(旧):methodName@url:line:col
|
|
64
|
-
* JavaScriptCore(iOS):methodName@url:line:col
|
|
65
|
-
*
|
|
66
|
-
* 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
|
|
67
|
-
*/
|
|
68
|
-
parseErrorStack(stack: string): MetroFrame[];
|
|
69
|
-
/**
|
|
70
|
-
* 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
|
|
71
|
-
*/
|
|
72
|
-
symbolicate(stack: MetroFrame[]): Promise<MetroFrame[] | null>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Sentry 事件捕获器。
|
|
76
|
-
* 封装 beforeSend / beforeBreadcrumb 逻辑,通过注入的回调将事件数据交给调用方处理。
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* const capture = new SentryCapture({
|
|
80
|
-
* onError(info) { console.log(info.message, info.symbolicatedFrames); },
|
|
81
|
-
* onNetwork(info) { console.log(info.method, info.url, info.statusCode); },
|
|
82
|
-
* });
|
|
83
|
-
* Sentry.init({ dsn: '...', beforeSend: capture.beforeSend, beforeBreadcrumb: capture.beforeBreadcrumb });
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* // 单测:注入 mock symbolicator
|
|
87
|
-
* const mockSym = { parseErrorStack: jest.fn(() => []), symbolicate: jest.fn(async () => null) };
|
|
88
|
-
* const onError = jest.fn();
|
|
89
|
-
* const capture = new SentryCapture({ onError, symbolicator: mockSym });
|
|
90
|
-
* await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
|
|
91
|
-
* expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'oops' }));
|
|
92
|
-
*/
|
|
93
|
-
declare class SentryCapture {
|
|
94
|
-
private readonly onError;
|
|
95
|
-
private readonly onNetwork;
|
|
96
|
-
private readonly symbolicator;
|
|
97
|
-
constructor({ onError, onNetwork, symbolicator }?: SentryCaptureOptions);
|
|
98
|
-
/**
|
|
99
|
-
* Sentry beforeSend hook。
|
|
100
|
-
* 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
|
|
101
|
-
* 符号化完成后触发 onError 回调,原样返回 event。
|
|
102
|
-
*/
|
|
103
|
-
beforeSend(event: ErrorEvent, hint: EventHint): Promise<ErrorEvent>;
|
|
104
|
-
/**
|
|
105
|
-
* Sentry beforeBreadcrumb hook。
|
|
106
|
-
* 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
|
|
107
|
-
*/
|
|
108
|
-
beforeBreadcrumb(breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { type ErrorCaptureInfo, type MetroFrame, MetroSymbolicator, type NetworkCaptureInfo, SentryCapture, type SentryCaptureOptions };
|
|
2
|
+
export { }
|
package/dist/index.d.ts
CHANGED
|
@@ -1,111 +1,2 @@
|
|
|
1
|
-
import { ErrorEvent, EventHint, Breadcrumb, BreadcrumbHint } from '@sentry/core';
|
|
2
1
|
|
|
3
|
-
|
|
4
|
-
* Sentry Capture 模块
|
|
5
|
-
*
|
|
6
|
-
* 提供 Metro 符号化(MetroSymbolicator)和 Sentry 事件捕获(SentryCapture)。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Metro /symbolicate 接口的帧格式(请求与响应共用)。 */
|
|
10
|
-
interface MetroFrame {
|
|
11
|
-
file: string;
|
|
12
|
-
lineNumber: number;
|
|
13
|
-
column: number;
|
|
14
|
-
methodName: string;
|
|
15
|
-
}
|
|
16
|
-
/** SentryCapture.onError 回调参数 */
|
|
17
|
-
interface ErrorCaptureInfo {
|
|
18
|
-
message: string;
|
|
19
|
-
symbolicatedFrames: MetroFrame[] | null;
|
|
20
|
-
componentStack: string | null;
|
|
21
|
-
/** Sentry Mechanism.type,如 'onerror' / 'generic' / 'auto.function.react.error_boundary' */
|
|
22
|
-
mechanismType: string | null;
|
|
23
|
-
}
|
|
24
|
-
/** SentryCapture.onNetwork 回调参数 */
|
|
25
|
-
interface NetworkCaptureInfo {
|
|
26
|
-
method: string | null;
|
|
27
|
-
url: string | null;
|
|
28
|
-
statusCode: number | null;
|
|
29
|
-
}
|
|
30
|
-
interface SentryCaptureOptions {
|
|
31
|
-
onError?: (info: ErrorCaptureInfo) => void;
|
|
32
|
-
onNetwork?: (info: NetworkCaptureInfo) => void;
|
|
33
|
-
/** 可注入 mock 实例以在单元测试中隔离网络依赖 */
|
|
34
|
-
symbolicator?: MetroSymbolicator;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Metro 符号化器。
|
|
38
|
-
* 将 bundle 行列号还原为源码位置(通过 Metro /symbolicate 接口)。
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* const sym = new MetroSymbolicator();
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* // 单测:注入 baseUrl 并 mock fetch
|
|
45
|
-
* const sym = new MetroSymbolicator({ baseUrl: 'http://localhost:8081' });
|
|
46
|
-
* global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ stack: [...] }) });
|
|
47
|
-
*/
|
|
48
|
-
declare class MetroSymbolicator {
|
|
49
|
-
readonly baseUrl: string;
|
|
50
|
-
constructor({ baseUrl }?: {
|
|
51
|
-
baseUrl?: string;
|
|
52
|
-
});
|
|
53
|
-
/**
|
|
54
|
-
* 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
|
|
55
|
-
* web 平台使用相对路径;native 回退到 localhost:8081。
|
|
56
|
-
*/
|
|
57
|
-
private static _getDefaultBaseUrl;
|
|
58
|
-
/**
|
|
59
|
-
* 解析 Error.stack 字符串为 Metro 所需的帧数组。
|
|
60
|
-
*
|
|
61
|
-
* 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
|
|
62
|
-
* V8/Node.js/Hermes(新):at methodName (url:line:col)
|
|
63
|
-
* Gecko/Firefox/Hermes(旧):methodName@url:line:col
|
|
64
|
-
* JavaScriptCore(iOS):methodName@url:line:col
|
|
65
|
-
*
|
|
66
|
-
* 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
|
|
67
|
-
*/
|
|
68
|
-
parseErrorStack(stack: string): MetroFrame[];
|
|
69
|
-
/**
|
|
70
|
-
* 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
|
|
71
|
-
*/
|
|
72
|
-
symbolicate(stack: MetroFrame[]): Promise<MetroFrame[] | null>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Sentry 事件捕获器。
|
|
76
|
-
* 封装 beforeSend / beforeBreadcrumb 逻辑,通过注入的回调将事件数据交给调用方处理。
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* const capture = new SentryCapture({
|
|
80
|
-
* onError(info) { console.log(info.message, info.symbolicatedFrames); },
|
|
81
|
-
* onNetwork(info) { console.log(info.method, info.url, info.statusCode); },
|
|
82
|
-
* });
|
|
83
|
-
* Sentry.init({ dsn: '...', beforeSend: capture.beforeSend, beforeBreadcrumb: capture.beforeBreadcrumb });
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* // 单测:注入 mock symbolicator
|
|
87
|
-
* const mockSym = { parseErrorStack: jest.fn(() => []), symbolicate: jest.fn(async () => null) };
|
|
88
|
-
* const onError = jest.fn();
|
|
89
|
-
* const capture = new SentryCapture({ onError, symbolicator: mockSym });
|
|
90
|
-
* await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
|
|
91
|
-
* expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'oops' }));
|
|
92
|
-
*/
|
|
93
|
-
declare class SentryCapture {
|
|
94
|
-
private readonly onError;
|
|
95
|
-
private readonly onNetwork;
|
|
96
|
-
private readonly symbolicator;
|
|
97
|
-
constructor({ onError, onNetwork, symbolicator }?: SentryCaptureOptions);
|
|
98
|
-
/**
|
|
99
|
-
* Sentry beforeSend hook。
|
|
100
|
-
* 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
|
|
101
|
-
* 符号化完成后触发 onError 回调,原样返回 event。
|
|
102
|
-
*/
|
|
103
|
-
beforeSend(event: ErrorEvent, hint: EventHint): Promise<ErrorEvent>;
|
|
104
|
-
/**
|
|
105
|
-
* Sentry beforeBreadcrumb hook。
|
|
106
|
-
* 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
|
|
107
|
-
*/
|
|
108
|
-
beforeBreadcrumb(breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export { type ErrorCaptureInfo, type MetroFrame, MetroSymbolicator, type NetworkCaptureInfo, SentryCapture, type SentryCaptureOptions };
|
|
2
|
+
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,161 +1,2 @@
|
|
|
1
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
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var src_exports = {};
|
|
22
|
-
__export(src_exports, {
|
|
23
|
-
MetroSymbolicator: () => MetroSymbolicator,
|
|
24
|
-
SentryCapture: () => SentryCapture
|
|
25
|
-
});
|
|
26
|
-
module.exports = __toCommonJS(src_exports);
|
|
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
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
157
|
-
0 && (module.exports = {
|
|
158
|
-
MetroSymbolicator,
|
|
159
|
-
SentryCapture
|
|
160
|
-
});
|
|
161
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,133 +1 @@
|
|
|
1
|
-
// src/capture.ts
|
|
2
|
-
import { parse as parseStackTrace } from "stacktrace-parser";
|
|
3
|
-
import { NativeModules, Platform } from "react-native";
|
|
4
|
-
var MetroSymbolicator = class _MetroSymbolicator {
|
|
5
|
-
constructor({ baseUrl } = {}) {
|
|
6
|
-
this.baseUrl = baseUrl ?? _MetroSymbolicator._getDefaultBaseUrl();
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
|
|
10
|
-
* web 平台使用相对路径;native 回退到 localhost:8081。
|
|
11
|
-
*/
|
|
12
|
-
static _getDefaultBaseUrl() {
|
|
13
|
-
try {
|
|
14
|
-
const scriptURL = NativeModules?.SourceCode?.scriptURL;
|
|
15
|
-
if (scriptURL) {
|
|
16
|
-
const url = new URL(scriptURL);
|
|
17
|
-
return `${url.protocol}//${url.host}`;
|
|
18
|
-
}
|
|
19
|
-
} catch (_) {
|
|
20
|
-
}
|
|
21
|
-
return Platform.OS === "web" ? "" : "http://localhost:8081";
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* 解析 Error.stack 字符串为 Metro 所需的帧数组。
|
|
25
|
-
*
|
|
26
|
-
* 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
|
|
27
|
-
* V8/Node.js/Hermes(新):at methodName (url:line:col)
|
|
28
|
-
* Gecko/Firefox/Hermes(旧):methodName@url:line:col
|
|
29
|
-
* JavaScriptCore(iOS):methodName@url:line:col
|
|
30
|
-
*
|
|
31
|
-
* 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
|
|
32
|
-
*/
|
|
33
|
-
parseErrorStack(stack) {
|
|
34
|
-
return parseStackTrace(stack).filter(
|
|
35
|
-
(f) => f.file != null && f.lineNumber != null
|
|
36
|
-
).map((f) => ({
|
|
37
|
-
file: f.file,
|
|
38
|
-
lineNumber: f.lineNumber,
|
|
39
|
-
column: f.column != null ? f.column - 1 : 0,
|
|
40
|
-
methodName: f.methodName === "<unknown>" ? "(anonymous)" : f.methodName
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
|
|
45
|
-
*/
|
|
46
|
-
async symbolicate(stack) {
|
|
47
|
-
if (stack.length === 0) return null;
|
|
48
|
-
const endpoint = `${this.baseUrl}/symbolicate`;
|
|
49
|
-
try {
|
|
50
|
-
const res = await fetch(endpoint, {
|
|
51
|
-
method: "POST",
|
|
52
|
-
headers: { "Content-Type": "application/json" },
|
|
53
|
-
body: JSON.stringify({ stack })
|
|
54
|
-
});
|
|
55
|
-
if (!res.ok) {
|
|
56
|
-
console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
const data = await res.json();
|
|
60
|
-
return data.stack ?? null;
|
|
61
|
-
} catch (e) {
|
|
62
|
-
console.warn("[SentryCapture] symbolication failed:", e?.message ?? e);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
var SentryCapture = class {
|
|
68
|
-
constructor({ onError, onNetwork, symbolicator } = {}) {
|
|
69
|
-
this.onError = onError;
|
|
70
|
-
this.onNetwork = onNetwork;
|
|
71
|
-
this.symbolicator = symbolicator ?? new MetroSymbolicator();
|
|
72
|
-
this.beforeSend = this.beforeSend.bind(this);
|
|
73
|
-
this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Sentry beforeSend hook。
|
|
77
|
-
* 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
|
|
78
|
-
* 符号化完成后触发 onError 回调,原样返回 event。
|
|
79
|
-
*/
|
|
80
|
-
async beforeSend(event, hint) {
|
|
81
|
-
globalThis.__SENTRY_LAST_RAW__ = { event, hint };
|
|
82
|
-
const message = event.exception?.values?.[0]?.value ?? event.message ?? "(unknown)";
|
|
83
|
-
let symbolicatedFrames = null;
|
|
84
|
-
const originalException = hint?.originalException;
|
|
85
|
-
const rawStack = originalException instanceof Error && typeof originalException.stack === "string" ? originalException.stack : null;
|
|
86
|
-
if (rawStack !== null) {
|
|
87
|
-
const frames = this.symbolicator.parseErrorStack(rawStack);
|
|
88
|
-
if (frames.length > 0) {
|
|
89
|
-
symbolicatedFrames = await this.symbolicator.symbolicate(frames);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (symbolicatedFrames === null) {
|
|
93
|
-
const sentryFrames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
|
|
94
|
-
const mapped = sentryFrames.filter(
|
|
95
|
-
(f) => f.filename != null && f.lineno != null
|
|
96
|
-
).map(
|
|
97
|
-
(f) => ({
|
|
98
|
-
file: f.filename,
|
|
99
|
-
lineNumber: f.lineno,
|
|
100
|
-
column: f.colno ?? 0,
|
|
101
|
-
methodName: f.function ?? "(anonymous)"
|
|
102
|
-
})
|
|
103
|
-
);
|
|
104
|
-
if (mapped.length > 0) {
|
|
105
|
-
symbolicatedFrames = await this.symbolicator.symbolicate(mapped);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const componentStack = event.contexts?.react?.componentStack ?? null;
|
|
109
|
-
const mechanismType = event.exception?.values?.[0]?.mechanism?.type ?? null;
|
|
110
|
-
this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });
|
|
111
|
-
return event;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Sentry beforeBreadcrumb hook。
|
|
115
|
-
* 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
|
|
116
|
-
*/
|
|
117
|
-
beforeBreadcrumb(breadcrumb, _hint) {
|
|
118
|
-
if (breadcrumb.type === "http") {
|
|
119
|
-
const data = breadcrumb.data;
|
|
120
|
-
this.onNetwork?.({
|
|
121
|
-
method: data?.method ?? null,
|
|
122
|
-
url: data?.url ?? null,
|
|
123
|
-
statusCode: data?.status_code ?? null
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
return breadcrumb;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
export {
|
|
130
|
-
MetroSymbolicator,
|
|
131
|
-
SentryCapture
|
|
132
|
-
};
|
|
133
1
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -4,12 +4,10 @@ var import_lgui_control = require("./lgui-control");
|
|
|
4
4
|
var import_css_control = require("./css-control");
|
|
5
5
|
var import_router_control = require("./router-control");
|
|
6
6
|
var import_screenshot_control = require("./screenshot-control");
|
|
7
|
-
var import_preview_control = require("./preview-control");
|
|
8
7
|
globalThis.__DEVKIT_INJECTED__ = true;
|
|
9
8
|
(0, import_hmr_control.setupHMRMessageControl)();
|
|
10
9
|
(0, import_lgui_control.setupLGUIMessageControl)();
|
|
11
10
|
(0, import_css_control.setupCSSInjectionControl)();
|
|
12
11
|
(0, import_router_control.setupRouterControl)();
|
|
13
12
|
(0, import_screenshot_control.setupScreenshotControl)();
|
|
14
|
-
(0, import_preview_control.setupPreviewControl)();
|
|
15
13
|
//# sourceMappingURL=entry-inject.js.map
|
|
@@ -24,193 +24,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
|
|
25
25
|
// src/stubs/sentry-react-native-stub.ts
|
|
26
26
|
var RealSentry = __toESM(require("@sentry/react-native"));
|
|
27
|
-
|
|
28
|
-
// src/capture.ts
|
|
29
|
-
var import_stacktrace_parser = require("stacktrace-parser");
|
|
27
|
+
var import_core = require("@sentry/core");
|
|
30
28
|
var import_react_native = require("react-native");
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
}
|
|
29
|
+
var SANDBOX_MARKER = /* @__PURE__ */ Symbol.for("vite-sandbox.expo-browser-observability.v2");
|
|
30
|
+
var FALLBACK_DSN = process.env["INJECT_SENTRY_DSN"] ?? "https://stubPublicKey@o0.ingest.sentry.io/0";
|
|
31
|
+
function reportVersionMismatch(expected, actual) {
|
|
32
|
+
const message = `Sentry SDK \u7248\u672C\u4E0D\u4E00\u81F4\uFF1Asandbox=${expected}\uFF0C@sentry/react-native core=${actual || "unknown"}\u3002\u89C2\u6D4B\u4E8B\u4EF6\u53EF\u80FD\u9759\u9ED8\u4E22\u5931\u3002`;
|
|
33
|
+
console.error(`[miaoda-expo-devkit] ${message}`);
|
|
166
34
|
}
|
|
167
|
-
var OVERRIDE_DSN = process.env["INJECT_SENTRY_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
35
|
var stub = {
|
|
193
36
|
...RealSentry,
|
|
194
37
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
38
|
+
* Native 保留应用初始化;独立 Web 使用安全 DSN;sandbox Web 已由前置运行时
|
|
39
|
+
* 初始化,因此这里只校验版本并阻止第二次初始化。
|
|
197
40
|
*/
|
|
198
41
|
init: ((options) => {
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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);
|
|
42
|
+
if (import_react_native.Platform.OS !== "web") return RealSentry.init(options);
|
|
43
|
+
const sandboxVersion = globalThis[SANDBOX_MARKER];
|
|
44
|
+
if (typeof sandboxVersion === "string") {
|
|
45
|
+
if (sandboxVersion !== import_core.SDK_VERSION) reportVersionMismatch(sandboxVersion, import_core.SDK_VERSION);
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
return RealSentry.init({ ...options, dsn: FALLBACK_DSN });
|
|
214
49
|
})
|
|
215
50
|
};
|
|
216
51
|
module.exports = stub;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miaoda-expo-devkit",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.113",
|
|
4
4
|
"description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -90,8 +90,7 @@
|
|
|
90
90
|
"lodash.throttle": "^4.1.1",
|
|
91
91
|
"metro-minify-esbuild": "^0.2.0",
|
|
92
92
|
"oxc-resolver": "^11.19.1",
|
|
93
|
-
"package-up": "^5.0.0"
|
|
94
|
-
"stacktrace-parser": "^0.1.11"
|
|
93
|
+
"package-up": "^5.0.0"
|
|
95
94
|
},
|
|
96
95
|
"peerDependencies": {
|
|
97
96
|
"@expo/config-plugins": ">=55.0.0",
|
|
@@ -29,50 +29,7 @@ diff --git a/build/src/start/server/metro/instantiateMetro.js b/build/src/start/
|
|
|
29
29
|
// This function ensures that modules in source maps are sorted in the same
|
|
30
30
|
// order as in a plain JS bundle.
|
|
31
31
|
metro._getSortedModules = function(graph) {
|
|
32
|
-
@@ -
|
|
33
|
-
// Patch HMR Server to send more info to the `_createModuleId` function for deterministic module IDs and add support for serializing HMR updates the same as all other bundles.
|
|
34
|
-
hmrServer._prepareMessage = async function(group, options, changeEvent) {
|
|
35
|
-
// Fork of https://github.com/facebook/metro/blob/3b3e0aaf725cfa6907bf2c8b5fbc0da352d29efe/packages/metro/src/HmrServer.js#L327-L393
|
|
36
|
-
// with patch for `_createModuleId`.
|
|
37
|
-
const logger = !options.isInitialUpdate ? changeEvent == null ? void 0 : changeEvent.logger : null;
|
|
38
|
-
+ const hmrChangeID = `${changeEvent == null ? 'initial' : changeEvent.changeId}:${group.revisionId}`;
|
|
39
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
40
|
-
+ type: 'hmr_update_started',
|
|
41
|
-
+ changeID: hmrChangeID
|
|
42
|
-
+ });
|
|
43
|
-
try {
|
|
44
|
-
@@ -394,7 +399,13 @@
|
|
45
|
-
const revPromise = this._bundler.getRevision(group.revisionId);
|
|
46
|
-
if (!revPromise) {
|
|
47
|
-
+ const revisionError = new (_RevisionNotFoundError()).default(group.revisionId);
|
|
48
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
49
|
-
+ type: 'hmr_update_failed',
|
|
50
|
-
+ changeID: hmrChangeID,
|
|
51
|
-
+ error: revisionError
|
|
52
|
-
+ });
|
|
53
|
-
return {
|
|
54
|
-
type: 'error',
|
|
55
|
-
- body: (0, _formatBundlingError().default)(new (_RevisionNotFoundError()).default(group.revisionId))
|
|
56
|
-
+ body: (0, _formatBundlingError().default)(revisionError)
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
@@ -428,3 +439,7 @@
|
|
60
|
-
});
|
|
61
|
-
logger == null ? void 0 : logger.point('serialize_end');
|
|
62
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
63
|
-
+ type: 'hmr_update_done',
|
|
64
|
-
+ changeID: hmrChangeID
|
|
65
|
-
+ });
|
|
66
|
-
return {
|
|
67
|
-
@@ -438,4 +453,6 @@
|
|
68
|
-
this._config.reporter.update({
|
|
69
|
-
type: 'bundling_error',
|
|
70
|
-
- error
|
|
71
|
-
+ error,
|
|
72
|
-
+ isHmr: true,
|
|
73
|
-
+ changeID: hmrChangeID
|
|
74
|
-
});
|
|
75
|
-
@@ -493,1 +515,1 @@
|
|
32
|
+
@@ -493,1 +498,1 @@
|
|
76
33
|
-//# sourceMappingURL=instantiateMetro.js.map
|
|
77
34
|
|
|
78
35
|
+//# sourceMappingURL=instantiateMetro.js.map
|
|
@@ -80,7 +37,7 @@ diff --git a/build/src/start/server/metro/aiErrorCollector.js b/build/src/start/
|
|
|
80
37
|
new file mode 100644
|
|
81
38
|
--- /dev/null
|
|
82
39
|
+++ b/build/src/start/server/metro/aiErrorCollector.js
|
|
83
|
-
@@ -0,0 +1,
|
|
40
|
+
@@ -0,0 +1,178 @@
|
|
84
41
|
+"use strict";
|
|
85
42
|
+Object.defineProperty(exports, "__esModule", {
|
|
86
43
|
+ value: true
|
|
@@ -187,7 +144,6 @@ new file mode 100644
|
|
|
187
144
|
+ const outputFile = getOutputPath(projectRoot);
|
|
188
145
|
+ let activeBuildID = null;
|
|
189
146
|
+ let lastFailedBuildID = null;
|
|
190
|
-
+ let activeHmrChangeID = null;
|
|
191
147
|
+ let state = {
|
|
192
148
|
+ schemaVersion: 1,
|
|
193
149
|
+ status: 'compiling',
|
|
@@ -223,41 +179,8 @@ new file mode 100644
|
|
|
223
179
|
+ if (!event || typeof event !== 'object') return;
|
|
224
180
|
+ const type = event.type;
|
|
225
181
|
+ const buildID = event.buildID ?? event.bundleDetails?.buildID ?? null;
|
|
226
|
-
+ if (type === 'hmr_update_started') {
|
|
227
|
-
+ activeHmrChangeID = typeof event.changeID === 'string' ? event.changeID : null;
|
|
228
|
-
+ lastFailedBuildID = null;
|
|
229
|
-
+ publish({
|
|
230
|
-
+ schemaVersion: 1,
|
|
231
|
-
+ status: 'compiling',
|
|
232
|
-
+ buildID: activeBuildID,
|
|
233
|
-
+ error: null
|
|
234
|
-
+ });
|
|
235
|
-
+ return;
|
|
236
|
-
+ }
|
|
237
|
-
+ if (type === 'hmr_update_failed') {
|
|
238
|
-
+ if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
|
|
239
|
-
+ publish({
|
|
240
|
-
+ schemaVersion: 1,
|
|
241
|
-
+ status: 'failed',
|
|
242
|
-
+ buildID: activeBuildID,
|
|
243
|
-
+ error: normalizeError(event.error, projectRoot, activeBuildID)
|
|
244
|
-
+ });
|
|
245
|
-
+ return;
|
|
246
|
-
+ }
|
|
247
|
-
+ if (type === 'hmr_update_done') {
|
|
248
|
-
+ if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
|
|
249
|
-
+ activeHmrChangeID = null;
|
|
250
|
-
+ publish({
|
|
251
|
-
+ schemaVersion: 1,
|
|
252
|
-
+ status: 'succeeded',
|
|
253
|
-
+ buildID: activeBuildID,
|
|
254
|
-
+ error: null
|
|
255
|
-
+ });
|
|
256
|
-
+ return;
|
|
257
|
-
+ }
|
|
258
182
|
+ if (type === 'bundle_build_started') {
|
|
259
183
|
+ activeBuildID = buildID;
|
|
260
|
-
+ activeHmrChangeID = null;
|
|
261
184
|
+ lastFailedBuildID = null;
|
|
262
185
|
+ publish({ schemaVersion: 1, status: 'compiling', buildID, error: null });
|
|
263
186
|
+ return;
|
|
@@ -274,16 +197,8 @@ new file mode 100644
|
|
|
274
197
|
+ return;
|
|
275
198
|
+ }
|
|
276
199
|
+ if (type === 'bundling_error') {
|
|
277
|
-
+ const
|
|
278
|
-
+ if (isHmrError && activeHmrChangeID != null && event.changeID !== activeHmrChangeID) return;
|
|
279
|
-
+ const errorBuildID = isHmrError ? activeBuildID : buildID ?? lastFailedBuildID ?? activeBuildID;
|
|
200
|
+
+ const errorBuildID = buildID ?? lastFailedBuildID ?? activeBuildID;
|
|
280
201
|
+ if (!isCurrentBuild(errorBuildID, true)) return;
|
|
281
|
-
+ if (isHmrError) {
|
|
282
|
-
+ // HMR 错误不携带 Metro build ID,保留变更标识以匹配后续恢复事件。
|
|
283
|
-
+ activeHmrChangeID = event.changeID ?? activeHmrChangeID;
|
|
284
|
-
+ }
|
|
285
|
-
+ lastFailedBuildID = errorBuildID;
|
|
286
|
-
+
|
|
287
202
|
+ publish({
|
|
288
203
|
+ schemaVersion: 1,
|
|
289
204
|
+ status: 'failed',
|
|
@@ -294,8 +209,6 @@ new file mode 100644
|
|
|
294
209
|
+ }
|
|
295
210
|
+ if (type === 'bundle_build_done') {
|
|
296
211
|
+ if (!isCurrentBuild(buildID)) return;
|
|
297
|
-
+ activeHmrChangeID = null;
|
|
298
|
-
+ lastFailedBuildID = null;
|
|
299
212
|
+ publish({ schemaVersion: 1, status: 'succeeded', buildID: buildID ?? activeBuildID, error: null });
|
|
300
213
|
+ }
|
|
301
214
|
+ }
|
|
@@ -1,60 +0,0 @@
|
|
|
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 preview_control_exports = {};
|
|
20
|
-
__export(preview_control_exports, {
|
|
21
|
-
setupPreviewControl: () => setupPreviewControl
|
|
22
|
-
});
|
|
23
|
-
module.exports = __toCommonJS(preview_control_exports);
|
|
24
|
-
function getParentOrigin() {
|
|
25
|
-
try {
|
|
26
|
-
const w = globalThis.window;
|
|
27
|
-
if (!w) return "*";
|
|
28
|
-
return new URLSearchParams(w.location.search).get("parentOrigin") || "*";
|
|
29
|
-
} catch {
|
|
30
|
-
return "*";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
function postPreviewSuccess() {
|
|
34
|
-
const w = globalThis.window;
|
|
35
|
-
if (!w || w.editorPreviewSuccessPosted) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
w.editorPreviewSuccessPosted = true;
|
|
39
|
-
w.parent.postMessage(
|
|
40
|
-
{
|
|
41
|
-
type: "editor-preview-success"
|
|
42
|
-
},
|
|
43
|
-
getParentOrigin()
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
function setupPreviewControl() {
|
|
47
|
-
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (document.readyState === "complete") {
|
|
51
|
-
postPreviewSuccess();
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
window.addEventListener("load", postPreviewSuccess, { once: true });
|
|
55
|
-
}
|
|
56
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
57
|
-
0 && (module.exports = {
|
|
58
|
-
setupPreviewControl
|
|
59
|
-
});
|
|
60
|
-
//# sourceMappingURL=preview-control.js.map
|