Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions docs/EMBEDDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,109 @@ module.exports = {
};
```

### Next.js (App Router / Turbopack)

ROSView is a browser-only component (it relies on Web Workers, WASM, `window`, etc.) and **cannot be server-rendered**. Integrating it into Next.js takes three steps:

**1. Transpile the package in `next.config.js`**

```js
// next.config.js
const nextConfig = {
transpilePackages: ['@ioai/rosview'],
};

module.exports = nextConfig;
```

**2. Wrap it in a Client Component loaded with `ssr: false`**

`ssr: false` is only allowed inside a **Client Component** — you cannot use it with `next/dynamic` directly in a Server Component (e.g. `page.tsx`). So create a `'use client'` wrapper:

```tsx
// components/RosViewerClient.tsx
'use client';

import '@ioai/rosview/style.css';
import dynamic from 'next/dynamic';

// Client-only load to avoid touching browser APIs during SSR
const RosViewer = dynamic(
() => import('@ioai/rosview').then((m) => ({ default: m.RosViewer })),
{ ssr: false },
);

export function RosViewerClient({ url }: { url: string }) {
return (
<div style={{ width: '100%', height: '100vh' }}>
<RosViewer url={url} theme="dark" />
</div>
);
}
```

```tsx
// app/visualize/page.tsx — a Server Component rendering the client wrapper
import { RosViewerClient } from '@/components/RosViewerClient';

export default async function VisualizePage({
searchParams,
}: {
searchParams: Promise<{ url?: string }>;
}) {
const { url } = await searchParams;
return <RosViewerClient url={url ?? ''} />;
}
```

**3. (Optional) Serve files through a Range-capable route**

To stream local/private mcap/bag recordings, expose a GET route that supports `Accept-Ranges` and point `url` at it:

```ts
// app/api/recording/route.ts
import fs from 'node:fs';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
const filePath = '/data/recording.mcap'; // validated, safe path
const { size } = await fs.promises.stat(filePath);
const range = req.headers.get('range');

// No Range: return the whole file but still advertise Range support
if (!range) {
const stream = fs.createReadStream(filePath);
return new NextResponse(stream as unknown as ReadableStream, {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': String(size),
'Accept-Ranges': 'bytes',
},
});
}

const [startStr, endStr] = range.replace('bytes=', '').split('-');
const start = Number(startStr);
const end = endStr ? Number(endStr) : size - 1;
const stream = fs.createReadStream(filePath, { start, end });

return new NextResponse(stream as unknown as ReadableStream, {
status: 206,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Range': `bytes ${start}-${end}/${size}`,
'Content-Length': String(end - start + 1),
'Accept-Ranges': 'bytes',
},
});
}
```

> **About `db3`:** ROSView does **not** support remote streaming of db3 (SQLite needs random access to the whole database). Download the db3 file in full as a `File` on the client and pass it via the `file` / `files` prop. mcap / bag can be streamed directly through the Range route above.

> **Version requirement:** Use **`@ioai/rosview` ≥ 1.3.5** (which depends on `@ioai/wasm-zstd` ≥ 1.1.2). 1.3.5 fixes the Turbopack inline-worker regression from 1.3.4 (`Failed to resolve module specifier './wasm-zstd-*.js'`, caused by the inline worker running from a `blob:` URL and unable to resolve the zstd glue's relative dynamic import). `@ioai/wasm-zstd` 1.1.2 statically inlines the glue into the worker, fixing this for good.

---

## Troubleshooting
Expand Down Expand Up @@ -281,6 +384,10 @@ Access-Control-Allow-Origin: https://your-app.example.com

Firefox has stricter CSP enforcement for workers. If you use a `Content-Security-Policy` header, add `worker-src 'self' blob:`.

### "Failed to resolve module specifier './wasm-zstd-*.js'"

This occurs with `@ioai/rosview` 1.3.4 (paired with `@ioai/wasm-zstd` 1.1.1) under bundlers that run inline workers from `blob:` URLs, such as Next.js Turbopack. Upgrade to **`@ioai/rosview` ≥ 1.3.5** (`@ioai/wasm-zstd` ≥ 1.1.2) — newer versions statically inline the zstd glue into the worker, removing the runtime relative dynamic import.

---

## TypeScript support
Expand Down
107 changes: 107 additions & 0 deletions docs/EMBEDDING.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,109 @@ module.exports = {
};
```

### Next.js(App Router / Turbopack)

ROSView 是纯浏览器端组件(依赖 Web Worker、WASM、`window` 等),**不能在服务端渲染**。在 Next.js 中集成时遵循以下三点即可:

**1. 在 `next.config.js` 中转译本包**

```js
// next.config.js
const nextConfig = {
transpilePackages: ['@ioai/rosview'],
};

module.exports = nextConfig;
```

**2. 用客户端组件包裹,并以 `ssr: false` 动态加载**

`ssr: false` 只能在**客户端组件**里使用,不能直接在服务端组件(如 `page.tsx`)里对 `next/dynamic` 使用。因此请新建一个 `'use client'` 包裹组件:

```tsx
// components/RosViewerClient.tsx
'use client';

import '@ioai/rosview/style.css';
import dynamic from 'next/dynamic';

// 仅在客户端加载,避免 SSR 阶段触碰浏览器 API
const RosViewer = dynamic(
() => import('@ioai/rosview').then((m) => ({ default: m.RosViewer })),
{ ssr: false },
);

export function RosViewerClient({ url }: { url: string }) {
return (
<div style={{ width: '100%', height: '100vh' }}>
<RosViewer url={url} theme="dark" />
</div>
);
}
```

```tsx
// app/visualize/page.tsx —— 服务端组件直接渲染上面的客户端组件
import { RosViewerClient } from '@/components/RosViewerClient';

export default async function VisualizePage({
searchParams,
}: {
searchParams: Promise<{ url?: string }>;
}) {
const { url } = await searchParams;
return <RosViewerClient url={url ?? ''} />;
}
```

**3.(可选)提供支持 HTTP Range 的文件接口**

要流式可视化本地/私有的 mcap/bag,可在 Next.js 中提供一个支持 `Accept-Ranges` 的 GET 路由,把 `url` 指向它即可:

```ts
// app/api/recording/route.ts
import fs from 'node:fs';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
const filePath = '/data/recording.mcap'; // 经过校验的安全路径
const { size } = await fs.promises.stat(filePath);
const range = req.headers.get('range');

// 无 Range:返回整文件,但仍声明支持 Range
if (!range) {
const stream = fs.createReadStream(filePath);
return new NextResponse(stream as unknown as ReadableStream, {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': String(size),
'Accept-Ranges': 'bytes',
},
});
}

const [startStr, endStr] = range.replace('bytes=', '').split('-');
const start = Number(startStr);
const end = endStr ? Number(endStr) : size - 1;
const stream = fs.createReadStream(filePath, { start, end });

return new NextResponse(stream as unknown as ReadableStream, {
status: 206,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Range': `bytes ${start}-${end}/${size}`,
'Content-Length': String(end - start + 1),
'Accept-Ranges': 'bytes',
},
});
}
```

> **关于 `db3`**:ROSView **不支持远程流式** db3(SQLite 需随机读取整库)。请在前端先把 db3 整文件下载为 `File`,再通过 `file` / `files` prop 传入。mcap / bag 则可直接用上面的 Range 接口流式加载。

> **版本要求**:请使用 **`@ioai/rosview` ≥ 1.3.5**(依赖 `@ioai/wasm-zstd` ≥ 1.1.2)。1.3.5 修复了 1.3.4 在 Turbopack 下因 inline worker 以 `blob:` URL 运行、无法解析 zstd glue 相对动态 import 而抛出的 `Failed to resolve module specifier './wasm-zstd-*.js'`;`@ioai/wasm-zstd` 1.1.2 将 glue 静态内联进 worker,彻底解决该问题。

---

## 故障排查
Expand Down Expand Up @@ -281,6 +384,10 @@ Access-Control-Allow-Origin: https://your-app.example.com

Firefox 对 Worker 的 CSP 更严格。若使用 `Content-Security-Policy` 响应头,请添加 `worker-src 'self' blob:`。

### "Failed to resolve module specifier './wasm-zstd-*.js'"

该错误出现在 `@ioai/rosview` 1.3.4(搭配 `@ioai/wasm-zstd` 1.1.1)在以 `blob:` URL 运行 inline worker 的打包器(如 Next.js Turbopack)下。升级到 **`@ioai/rosview` ≥ 1.3.5**(`@ioai/wasm-zstd` ≥ 1.1.2)即可修复——新版本会将 zstd glue 静态内联进 worker,不再有运行时相对动态 import。

---

## TypeScript 支持
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ioai/rosview",
"version": "1.3.4",
"version": "1.3.5",
"description": "High-performance robotics data visualization for MCAP, ROS bag, ROS2 db3, HDF5 and BVH — embeddable React component and standalone SPA",
"keywords": [
"ros",
Expand Down Expand Up @@ -97,7 +97,7 @@
"@foxglove/rosmsg-serialization": "^2.0.4",
"@foxglove/rosmsg2-serialization": "^3.0.3",
"@ioai/hdf5": "^1.0.0",
"@ioai/wasm-zstd": "^1.1.1",
"@ioai/wasm-zstd": "^1.1.2",
"@mcap/browser": "^1.1.0",
"@mcap/core": "^2.0.2",
"@playwright/test": "^1.59.1",
Expand Down
Loading