@huolala-tech/page-spy-lynx
Version:
An SDK of PageSpy for debugging Lynx app
191 lines (138 loc) • 6.71 kB
Markdown
[npm-image]: https://img.shields.io/npm/v/@huolala-tech/page-spy-lynx?logo=npm&label=version
[npm-url]: https://www.npmjs.com/package/@huolala-tech/page-spy-lynx
[minified-image]: https://img.shields.io/bundlephobia/min/@huolala-tech/page-spy-lynx
[minified-url]: https://unpkg.com/browse/@huolala-tech/page-spy-lynx/dist/esm/index.min.js
[English](./README.md) | [Chinese](./README_ZH.md)
# `@huolala-tech/page-spy-lynx`
[![SDK version][npm-image]][npm-url]
[![SDK size][minified-image]][minified-url]
> PageSpy SDK for Lynx apps.
Lynx does not provide browser `window` or `document` objects. This SDK does not depend on DOM APIs, and its built-in plugins enable themselves only when the Lynx runtime exposes the corresponding global capability.
## Usage
Initialize PageSpy from background-only code, such as an entry module imported only by background logic or inside a Lynx `useEffect`.
```ts
import PageSpy from '@huolala-tech/page-spy-lynx';
const pageSpy = new PageSpy({
api: 'example.com',
});
```
## `InitConfig`
The `api` option is required. Other options follow the shared PageSpy SDK config.
```ts
const pageSpy = new PageSpy(config);
interface InitConfig {
api: string;
project?: string;
title?: string;
enableSSL?: boolean | null;
disabledPlugins?: (InternalPlugins | string)[];
}
type InternalPlugins =
| 'ConsolePlugin'
| 'ErrorPlugin'
| 'NetworkPlugin'
| 'SystemPlugin'
| 'WebSocketPlugin';
```
## Built-in plugins
- `ConsolePlugin`: proxies `console.log/info/error/warn/debug`.
- `SystemPlugin`: reports PageSpy client and available Lynx system information.
- `NetworkPlugin`: proxies `fetch` first, and proxies `XMLHttpRequest` only when the runtime exposes a compatible implementation.
- `WebSocketPlugin`: proxies WebSocket traffic only when `globalThis.WebSocket` is constructable.
- `ErrorPlugin`: listens to global error and unhandled rejection hooks when the runtime exposes them.
Use `disabledPlugins` to turn off any built-in plugin.
## Native WebSocket module
PageSpy prefers Lynx `NativeModules.LynxNativeWebSocketModule` for its own WebSocket connection. Host apps should register this native module before initializing PageSpy in real Lynx native runtimes. In web preview runtimes, the SDK falls back to a constructable `globalThis.WebSocket`; if neither capability exists, initialization throws a clear error.
The JavaScript contract is:
```ts
declare let NativeModules: {
LynxNativeWebSocketModule: {
connect(socketId: string, url: string): void;
send(socketId: string, data: string): void;
close(socketId: string): void;
drainEvents(
socketId: string,
callback: (events: NativeWebSocketEvent[]) => void,
): void;
};
};
type NativeWebSocketEvent =
| { type: 'open'; socketId: string }
| { type: 'message'; socketId: string; data: string }
| { type: 'close'; socketId: string; code?: number; reason?: string }
| { type: 'error'; socketId: string; message?: string };
```
Native implementations should enqueue `open/message/close/error` events and return them through `drainEvents`. Do not depend on invoking the same NativeModule callback multiple times.
Native examples are included in `native-examples/android`, `native-examples/ios`, and `native-examples/harmony`.
Android registration:
```java
LynxEnv.inst().registerModule(
"LynxNativeWebSocketModule",
LynxNativeWebSocketModule.class
);
```
The Android example uses OkHttp's `WebSocketListener`; add OkHttp to the host app or adapt the sample to the app's existing WebSocket client.
iOS registration:
```objc
[globalConfig registerModule:LynxNativeWebSocketModule.class];
```
Harmony registration:
```ts
import { LynxNativeWebSocketModule } from './module/LynxNativeWebSocketModule';
this.modules.set('LynxNativeWebSocketModule', {
moduleClass: LynxNativeWebSocketModule,
});
```
The Harmony example uses NetworkKit's WebSocket API. If your Harmony SDK uses a different WebSocket import path, adapt only the import and keep the NativeModule contract unchanged.
`WebSocketPlugin` is separate from this module. It still proxies app WebSocket traffic only when the Lynx runtime exposes a constructable `globalThis.WebSocket`.
## Native Console module
In Lynx native runtimes, `console` messages are routed through the Lynx DevTool inspector pipeline and cannot be reliably intercepted by overwriting `console.log` in JavaScript. PageSpy automatically detects `NativeModules.PageSpyConsoleModule` and polls `drainMessages` for JSON strings received by `LynxInspectorConsoleDelegate.onConsoleMessage(msg)`.
The JavaScript contract is:
```ts
declare let NativeModules: {
PageSpyConsoleModule: {
drainMessages(callback: (messages: string[]) => void): void;
};
};
```
Native examples are included in `native-examples/android`, `native-examples/ios`, and `native-examples/harmony`:
- `PageSpyConsoleModule`: stores console messages and returns them to JS via `drainMessages`.
- `PageSpyConsoleDelegate`: implements `LynxInspectorConsoleDelegate` and queues messages from `onConsoleMessage(msg)`.
Android registration and setup:
```java
LynxEnv.inst().registerModule(
"PageSpyConsoleModule",
PageSpyConsoleModule.class
);
LynxBaseInspectorOwner owner = lynxView.getBaseInspectorOwner();
if (owner != null) {
owner.setLynxInspectorConsoleDelegate(new PageSpyConsoleDelegate());
}
```
iOS registration and setup:
```objc
[globalConfig registerModule:PageSpyConsoleModule.class];
id<LynxBaseInspectorOwner> owner = lynxView.baseInspectorOwner;
if (owner) {
[PageSpyConsoleDelegate installWithInspectorOwner:owner];
}
```
Harmony registration and setup:
```ts
import { LynxView, LynxContext } from '@lynx/lynx';
import { PageSpyConsoleModule } from './module/PageSpyConsoleModule';
import { PageSpyConsoleDelegate } from './module/PageSpyConsoleDelegate';
this.modules.set('PageSpyConsoleModule', {
moduleClass: PageSpyConsoleModule,
});
LynxView({
onCreate: (context: LynxContext) => {
const owner = context.getBaseInspectorOwner();
if (owner) {
PageSpyConsoleDelegate.installWithInspectorOwner(owner);
}
},
});
```
Without this delegate integration, PageSpy still works in Web or JS-overwritable console environments, but iOS/Android/Harmony native console messages may not be captured because they stay inside Lynx's native inspector pipeline.
Troubleshooting: if iOS logs only show `LynxNativeWebSocketModule.drainEvents` and never show `PageSpyConsoleModule.drainMessages`, the current LynxView does not expose `NativeModules.PageSpyConsoleModule`. Registering only the WebSocket module makes the PageSpy connection work, but it does not capture native console messages.