remote-webview-debugger 0.0.0-stage → 0.2.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/CHANGELOG.zh-CN.md +66 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE +17 -0
  5. package/README.ar.md +185 -0
  6. package/README.de.md +169 -0
  7. package/README.es.md +169 -0
  8. package/README.fr.md +169 -0
  9. package/README.ja.md +169 -0
  10. package/README.ko.md +169 -0
  11. package/README.md +167 -2
  12. package/README.pt-BR.md +169 -0
  13. package/README.ru.md +169 -0
  14. package/README.zh-CN.md +169 -0
  15. package/README.zh-TW.md +169 -0
  16. package/dist/index.d.ts +368 -0
  17. package/dist/index.mjs +8319 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/locales/ar.d.ts +4 -0
  20. package/dist/locales/ar.js +2 -0
  21. package/dist/locales/ar.mjs +150 -0
  22. package/dist/locales/ar.mjs.map +1 -0
  23. package/dist/locales/de.d.ts +4 -0
  24. package/dist/locales/de.js +2 -0
  25. package/dist/locales/de.mjs +150 -0
  26. package/dist/locales/de.mjs.map +1 -0
  27. package/dist/locales/es.d.ts +4 -0
  28. package/dist/locales/es.js +2 -0
  29. package/dist/locales/es.mjs +150 -0
  30. package/dist/locales/es.mjs.map +1 -0
  31. package/dist/locales/fr.d.ts +4 -0
  32. package/dist/locales/fr.js +2 -0
  33. package/dist/locales/fr.mjs +150 -0
  34. package/dist/locales/fr.mjs.map +1 -0
  35. package/dist/locales/ja.d.ts +4 -0
  36. package/dist/locales/ja.js +2 -0
  37. package/dist/locales/ja.mjs +150 -0
  38. package/dist/locales/ja.mjs.map +1 -0
  39. package/dist/locales/ko.d.ts +4 -0
  40. package/dist/locales/ko.js +2 -0
  41. package/dist/locales/ko.mjs +156 -0
  42. package/dist/locales/ko.mjs.map +1 -0
  43. package/dist/locales/pt.d.ts +4 -0
  44. package/dist/locales/pt.js +2 -0
  45. package/dist/locales/pt.mjs +150 -0
  46. package/dist/locales/pt.mjs.map +1 -0
  47. package/dist/locales/ru.d.ts +4 -0
  48. package/dist/locales/ru.js +2 -0
  49. package/dist/locales/ru.mjs +150 -0
  50. package/dist/locales/ru.mjs.map +1 -0
  51. package/dist/locales/zh-TW.d.ts +4 -0
  52. package/dist/locales/zh-TW.js +2 -0
  53. package/dist/locales/zh-TW.mjs +150 -0
  54. package/dist/locales/zh-TW.mjs.map +1 -0
  55. package/dist/sdk.js +42 -0
  56. package/dist/sdk.js.map +1 -0
  57. package/package.json +76 -4
package/README.ko.md ADDED
@@ -0,0 +1,169 @@
1
+ <!-- i18n-source: d9a31993ea867d3ef7058e7d637886f38a6ee3e1 -->
2
+ [English](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | 한국어 | [Español](README.es.md) | [Français](README.fr.md) | [Deutsch](README.de.md) | [Português (Brasil)](README.pt-BR.md) | [Русский](README.ru.md) | [العربية](README.ar.md)
3
+
4
+ # RWD 페이지 SDK (`sdk/`)
5
+
6
+ 앱의 WebView나 H5 페이지가 불러오는 JS 파일 하나예요. 이 파일을 넣으면 AI 에이전트가 RWD 게이트웨이(`gateway/`)와 MCP를 거쳐 페이지를 살펴보고 조작할 수 있어요. 프로토콜: [`docs/protocol.ko.md`](../docs/protocol.ko.md), 아키텍처: [`docs/architecture.ko.md`](../docs/architecture.ko.md).
7
+
8
+ ```html
9
+ <script src="https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.1.0/dist/sdk.js"></script>
10
+ <script>
11
+ RemoteWebviewDebugger.run("https://rwd.example.com/v1", "rwd_pk_xxxxxxxx", {
12
+ userId: "u-42", // 선택
13
+ deviceId: "…", // 선택. SDK를 주입하는 네이티브 호스트가 넘겨 줌
14
+ helpButton: true, // 선택. “지원 요청” 버튼 표시
15
+ app: { name: "Shop", version: "3.2.0" },
16
+ lang: "en", // 선택. 언어 태그. 기본값은 브라우저의 언어 설정
17
+ }).then(({ deviceCode, status }) => console.log(deviceCode, status))
18
+ </script>
19
+ ```
20
+
21
+ ## SDK 받기
22
+
23
+ npm에 [`remote-webview-debugger`](https://www.npmjs.com/package/remote-webview-debugger)로 배포돼 있어요. 버전과 변경 사항: [CHANGELOG](CHANGELOG.md).
24
+
25
+ | 사용 방식 | 위치 |
26
+ |---|---|
27
+ | CDN에서 `<script>`로 | `https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.1.0/dist/sdk.js` 또는 `https://unpkg.com/remote-webview-debugger@0.1.0/dist/sdk.js` (압축된 IIFE, 전역 `RemoteWebviewDebugger`. 버전을 고정하고, 원하면 `integrity`도 추가하세요) |
28
+ | 번들러 | `npm install remote-webview-debugger` 후 `import { run, status } from 'remote-webview-debugger'` (타입이 포함된 ES 모듈). 수집이 일찍 시작되도록 직접 작성한 코드보다 먼저 import하세요. 같은 페이지에서 `<script>` 버전을 함께 불러오지 마세요 |
29
+ | 직접 운영하는 게이트웨이 | `https://<gateway>/v1/sdk.js`, 게이트웨이에 내장된 사본이에요 (SRI용 콘텐츠 주소 기반 URL도 있고, 콘솔에서 안내해요) |
30
+ | 네이티브 앱 | 패키지의 `dist/sdk.js`를 앱에 포함하거나 위 경로 중 하나에서 내려받으세요. [네이티브 연동](../docs/native-integration.ko.md)을 참고하세요 |
31
+
32
+ ## API
33
+
34
+ | API | 설명 |
35
+ |---|---|
36
+ | `run(gatewayUrl, appKey, opts?)` | 페이지를 온라인으로 만들어요 (기기 코드, 배지). `Promise<{deviceCode, status}>`를 반환하고, 여러 번 호출해도 문제없어요. 옵션은 아래를 보세요. |
37
+ | `status()` | `{deviceCode, status, control, help, label, deviceId, pageId, version, lang, deviceToken, level, config, pending, logSession}`. `status`는 `idle / connecting / online / polling / stopped / rejected / quota` 중 하나, `lang`은 UI 언어 (`run()` 전에는 `null`), `logSession`은 이 페이지가 기록 중인 세션 로그 (기록하지 않을 때는 `null`) |
38
+ | `stop()` | 연결을 끊고 UI를 제거해요 (console / 네트워크 수집은 상한 안에서 계속돼요) |
39
+ | `setUser(userId)` | 사용자를 바꿔요. 새 `hello`로 한 번 다시 연결해요 |
40
+ | `help(on)` | 지원 요청 / 요청 취소 |
41
+ | `nativeControl(nativeKey, action)` | 네이티브 호스트의 자체 UI에서 직접 조작 / AI에 맡기기 / 종료 |
42
+ | `nativeDecide(nativeKey, requestId, value)` | `nativeApproval`을 쓸 때 `status().pending`에 응답해요. 접속 요청에는 권한 수준 또는 `null`, 민감한 작업 확인에는 `'allow'` 또는 `null` |
43
+ | `setLang(lang?)` | 실행 중에 UI 언어를 바꿔요 (앱 자체의 언어 설정 등). 인자 없이 호출하면 브라우저의 언어 설정으로 돌아가요. [언어](#언어)를 참고하세요 |
44
+ | `registerLocale(pack)` | 언어 팩을 추가하거나 같은 `id`의 언어 팩을 교체해요 (배열도 받아요). [언어](#언어)를 참고하세요 |
45
+ | `builtinLocales` | 내장된 언어 팩의 id 목록 |
46
+
47
+ `run()` 옵션:
48
+
49
+ | 옵션 | 설명 |
50
+ |---|---|
51
+ | `userId` | 로그인한 사용자. `hello`와 함께 전송돼요 |
52
+ | `deviceId` | 네이티브 호스트가 주는 고정 기기 id (없으면 localStorage에 하나를 만들어 보관해요) |
53
+ | `helpButton` | “지원 요청” 버튼 표시 (프로젝트 정책과 OR로 합쳐져요) |
54
+ | `app` | 호스트 앱의 `{ name, version }` |
55
+ | `lang` | 휴대폰 사용자가 보는 모든 것(배지, 플로팅 창, 하단 바, 대화 상자, 토스트, eruda 탭, 민감한 작업의 “AI가 …을(를) 요청했어요” 설명)의 언어. `'ja'`나 `'pt-BR'` 같은 언어 태그예요. 기본값은 브라우저의 언어 설정이에요. [언어](#언어)를 참고하세요 |
56
+ | `locales` | 추가 언어 팩. 언어를 고르기 전에 등록돼요 (`registerLocale`과 같아요) |
57
+ | `messages` | 언어별로 덮어쓸 메시지: `{ en: { ui: { help: 'Contact support' } }, zh: { ui: { help: '联系客服' } } }` |
58
+ | `networkExclude` | 에이전트가 일일이 볼 필요 없는 요청. 늘 404가 나는 폰트, 분석용 비콘 등: `['.ttf', 'WebTracks', '*beacon*', '/\\/track\\//']`. 각 규칙은 URL에 대해 대소문자를 구분하지 않는 부분 문자열, `*` 와일드카드 패턴, 또는 `/…/`로 쓰면 정규식으로 매칭돼요. 프로젝트 정책의 규칙에 더해져요. 매칭된 요청은 네트워크 목록과 `net_fail` 타임라인 이벤트에서 빠지지만 페이지는 계속 기록하므로, 에이전트가 `includeExcluded`로 요청할 수 있어요 |
59
+ | `lockScreen` | `false`: 에이전트가 접속해 있는 동안 페이지를 잠그지 않아요 (사용자는 플로팅 창에서 언제든 바꿀 수 있어요). 기본값 `true` |
60
+ | `theme` | `'auto'` (기본값, 페이지 배경을 따라감), `'system'`, `'light'`, `'dark'` |
61
+ | `style` | UI의 색상, 폰트, 모서리 반경, z-index, 위치: `{ accent: '#e11d48', radius: 12, zIndex: 1000, pill: { side: 'left' } }`. [스타일](#스타일)을 참고하세요 |
62
+ | `eruda` | `true` 또는 `{ dock, load, src, name }`: Remote Debugger를 eruda 안의 탭으로 보여 줘요. `load`는 페이지에 eruda가 없을 때 최신 eruda를 불러와요. 기본적으로 꺼져 있고, 프로젝트 정책으로 원격에서 켤 수 있어요. 필드별로 로컬 옵션이 우선해요 |
63
+ | `record` | `false`: 이 페이지는 세션 로그를 쓰지 않아요 (이미 저장된 것은 지우지 않아요). 기본값 `true`. 프로젝트 정책으로 모든 페이지의 로그를 끌 수 있어요 |
64
+ | `launchId` | 이 페이지가 앱의 어느 실행에 속하는지. 앱 프로세스 하나의 모든 WebView가 세션 로그를 한 세션 아래에 기록하게 해요. 네이티브 호스트는 자체 id를 여기에 넘기거나 `window.RWDLaunchId`를 문자열 또는 문자열을 반환하는 함수로 정의해요. 그렇지 않으면 SDK가 세션 쿠키와 하트비트로 실행을 추정해요 |
65
+ | `nativeKey`, `deviceToken`, `nativeApproval` | 네이티브 호스트용: `nativeControl` / `nativeDecide`에 쓰는 비밀 값, 호스트가 보관하는 기기 토큰, 접속 / 확인 질문을 네이티브 UI로 묻기 |
66
+
67
+ ## 언어
68
+
69
+ 내장 언어: 영어(`en`), 중국어 간체(`zh`), 중국어 번체(`zh-TW`, `zh-HK`, `zh-MO`, `zh-Hant`에도 쓰여요), 프랑스어(`fr`), 스페인어(`es`), 러시아어(`ru`), 아랍어(`ar`, 오른쪽에서 왼쪽으로 배치), 일본어(`ja`), 한국어(`ko`), 독일어(`de`), 포르투갈어(`pt`, 브라질 포르투갈어, `pt-PT`에도 쓰여요).
70
+
71
+ **언어 선택.** 휴대폰 사용자의 언어는 `run({ lang })`이 있으면 그것, 그다음 `navigator.languages`를 순서대로 봐요. SDK는 각 태그마다 태그 그대로, 그다음 더 짧은 형태(`pt-BR` → `pt`, `zh-HK` → `zh-TW` → `zh`)를 시도해서 언어 팩이 있는 첫 언어를 쓰고, 없으면 영어로 돌아가요. 언어 팩에 없는 메시지도 같은 방식으로 대체되므로 일부만 번역된 언어 팩도 괜찮아요. `setLang('ja')`로 실행 중에 바꿀 수 있고, `status().lang`이 사용 중인 언어 팩이에요.
72
+
73
+ **불러오기.** 영어와 중국어 간체는 `sdk.js`에 들어 있어요. 나머지 언어 팩은 페이지에서 처음 필요할 때 불러와요 (그 사이에는 UI가 대체 언어로 표시돼요):
74
+
75
+ - `<script>` 빌드: `sdk.js` 옆의 `locales/<id>.js`에서 불러오고 (CDN 경로라면 `…/remote-webview-debugger@0.1.0/dist/locales/ja.js`, 게이트웨이라면 `/v1/locales/ja.js`), 실패하면 `run()`에 넘긴 게이트웨이에서 불러와요. 추가 요청을 피하려면 `sdk.js` 앞이나 뒤에서 언어 팩을 직접 불러오세요: `<script src="…/dist/locales/ja.js"></script>`.
76
+ - ES 모듈 빌드: 동적 import를 쓰므로 번들러가 언어마다 별도 청크를 만들어요. 미리 번들에 넣으려면 `import ja from 'remote-webview-debugger/locales/ja'` 후 `run(url, key, { locales: [ja] })`로 넘기세요.
77
+
78
+ **직접 만든 언어 팩과 문구.** 언어 팩은 `{ id, name, dir?, ui?, action?, agent? }`예요. 빠진 부분은 대체 언어에서 가져와요:
79
+
80
+ ```js
81
+ RemoteWebviewDebugger.registerLocale({
82
+ id: 'it',
83
+ name: 'Italiano',
84
+ ui: { takeover: 'Prendi il controllo', end: 'Termina', help: 'Richiedi assistenza' /* … */ },
85
+ })
86
+ RemoteWebviewDebugger.run(url, key, {
87
+ messages: { en: { ui: { help: 'Contact support' } } }, // 어떤 언어든 개별 메시지만 바꾸기
88
+ })
89
+ ```
90
+
91
+ `ui`는 UI 문구, `action`은 확인 대화 상자의 “AI가 …을(를) 요청했어요” 부분을 채우는 문구, `agent`는 에이전트에게 돌아가는 문구예요. TypeScript 타입(`LocalePack`, `Messages`, `UiMessages`, `ActionMessages`, `AgentMessages`)에 모든 키가 나와 있고, `src/locales/en.ts`가 기준이에요.
92
+
93
+ **두 종류의 독자.** 휴대폰 사용자는 위에서 고른 언어로 봐요. 에이전트는 RPC 오류 메시지와 타임라인 요약(탭, 페이지 내 이동, 실패한 요청)을 게이트웨이의 언어 `welcome.agentLang`(`RWD_LANG`)으로 읽고, 이 언어 팩도 같은 방식으로 불러와요. welcome이 다른 값을 알려 주기 전까지는 영어예요. 마지막 값은 localStorage `rwd:alang`에 보관되므로, 다음 페이지의 welcome이 오기 전의 이벤트도 그 언어를 써요.
94
+
95
+ 그래서 중국어를 쓰는 사용자가 영어로 동작하는 에이전트와 함께 작업할 수 있어요. 대화 상자는 `AI 想要点击「提交订单」,允许吗?`라고 묻고, 사용자가 거부하면 에이전트는 `The user denied this action`을 받아요.
96
+
97
+ ## 스타일
98
+
99
+ UI는 `#__rwd`(`<body>` 바깥)의 shadow root 안에 있어서, 페이지 CSS는 아래 경로를 통해서만 UI에 닿아요:
100
+
101
+ - `:root`나 `#__rwd`에 지정하는 **CSS 사용자 지정 속성**. 예: `:root { --rwd-accent-rgb: 225, 29, 72; --rwd-radius: 12px; }`.
102
+ - **`run({ style })`**: 같은 속성을 `#__rwd`에 설정해요. 여기서는 색상에 어떤 CSS 색상 값이든 쓸 수 있어요.
103
+ - 그 밖의 것은 **`::part()`**: `#__rwd::part(bar) { … }`. part: `pill`, `window`, `bar`, `task`, `dialog`, `toast`, `button`, `cursor`, `glow`, `overlay`. 내부의 클래스 이름은 안정적인 API가 아니에요.
104
+ - **`theme`**: 밝은 유리 또는 어두운 유리 스타일 (기본값: 페이지 배경을 따라감).
105
+
106
+ | 속성 | `style` 필드 | 기본값 | 적용 대상 |
107
+ |---|---|---|---|
108
+ | `--rwd-accent-rgb` | `accent` | `59, 130, 246` | 버튼, 스위치, 에이전트의 포인터, 강조 표시, 가장자리 글로우 |
109
+ | `--rwd-danger-rgb` | `danger` | `239, 68, 68` | 종료 버튼 |
110
+ | `--rwd-warning-rgb` | `warning` | `245, 158, 11` | 지원 요청과 지원 중, 사용자 직접 조작 중 상태 |
111
+ | `--rwd-success-rgb` | `success` | `34, 197, 94` | 온라인 표시 점, “복사됨” |
112
+ | `--rwd-font` | `font` | 시스템 UI 폰트 (중국어, 일본어, 한국어는 언어별) | 모든 텍스트 |
113
+ | `--rwd-font-mono` | `monoFont` | `ui-monospace, "SF Mono", …` | 기기 코드와 태그 |
114
+ | `--rwd-radius` | `radius` | `18px` | 플로팅 창. 대화 상자는 2px 더 둥글고, 작업 카드와 배지는 2px 덜, 대화 상자 버튼은 6px 덜 둥글어요 |
115
+ | `--rwd-button-radius` | `buttonRadius` | `999px` | 버튼, 하단 바, 토스트 |
116
+ | `--rwd-z-index` | `zIndex` | `2147483647` | UI 전체 |
117
+ | `--rwd-bar-bottom` | `barBottom` | `14px` | 하단 바, 하단 안전 영역 위 |
118
+ | `--rwd-task-top` | `taskTop` | `10px` | 작업 카드, 상단 안전 영역 아래 |
119
+ | `--rwd-toast-bottom` | `toastBottom` | `84px` | 토스트, 하단 안전 영역 위 |
120
+ | — | `pill: { side, top }` | 오른쪽 가장자리 (오른쪽에서 왼쪽으로 쓰는 언어에서는 왼쪽), `0.62` | 사용자가 끌어서 옮기기 전까지 대기 중인 배지가 처음 놓이는 위치. `top`은 0(맨 위)부터 1(맨 아래)까지 |
121
+
122
+ 색상은 `r, g, b` 세 값으로 지정해서 반투명한 색조도 함께 바뀌어요 (Bootstrap의 `--bs-*-rgb`와 같은 방식).
123
+
124
+ ## 페이지에서의 동작 (프로토콜 §6–§8, §11)
125
+
126
+ - 권한 수준: RPC는 `welcome.level` / `control.level`에 따라 검사돼요 (`forbidden`, "Requires the … level"). 권한 수준이 없는 에이전트(구버전 게이트웨이)는 `interact`로 취급하고, 접속한 에이전트가 없으면 추가로 강제하는 것은 없어요.
127
+ - 기기 토큰은 localStorage `rwd:dtoken`에, 재개 토큰은 sessionStorage `rwd:resume`(pageId에 묶임)에 보관해요. 종료 코드 4401: 그 사이에 토큰이 바뀌었으면(같은 기기의 다른 탭이 방금 받아 왔으면) 바로 한 번 재시도하고, 아니면 `rejected`가 되고 약 10분 뒤에 재시도해요. 4409: 새 pageId로 다시 연결해요. 4429: `quota`가 되고 약 5분 뒤에 재시도해요.
128
+ - 주소창의 `rwd_invite`는 `run()` 때 제거되고(`history.replaceState`) welcome이 올 때까지 hello와 함께 전송돼요.
129
+ - `welcome.config`: `helpButton`(`run()` 옵션과 OR), `maskSelectors`, `maskPatterns`(스냅샷, console, 네트워크 URL, 저장소 값, 타임라인 요약), `confirmSelectors`, `networkExclude`(`run()` 옵션의 규칙에 더해짐), `sessionLog`. 마지막 config는 localStorage `rwd:cfg`에 보관돼 welcome이 올 때까지 쓰여요. 그래서 세션 로그를 끈 프로젝트는 다음 페이지에서도 welcome이 오기 전 첫 순간부터 로그가 꺼져 있어요.
130
+ - 스냅샷은 마지막에 `[Overlays]` 섹션이 붙어서 팝업의 배경, 모달, 바텀 시트처럼 페이지 위에 떠 있는 것을 나열해요. 각 항목에는 태그, 짧은 텍스트, 뷰포트를 덮는 비율, ref가 있어요. 배경은 aria 스냅샷에 노드가 없는 빈 요소라서 `eN` 대신 `oN` ref를 받아요. 덕분에 에이전트가 배경을 탭해서 팝업을 닫을 수 있어요.
131
+ - 클릭은 먼저 요소에서 보이는 부분의 중앙을 노리고, 그다음 요소 안 3×3 격자의 다른 지점을 노려요. 그래서 버튼 위에 배지가 있어도 막히지 않아요. 모든 지점이 가려져 있으면 RPC는 `not_interactable`로 실패하고, 가리고 있는 요소와 그 ref를 알려 줘요.
132
+ - 접속 대화 상자와 민감한 작업 대화 상자(`[data-rwd-confirm]`, `confirmSelectors`, Enter로 폼을 제출하는 경우 포함)는 Shadow DOM 안에 있고, 신뢰할 수 있는 탭만 받으며, 나타난 지 0.4초 뒤에 활성화돼요. 같은 출처의 탭들은 BroadcastChannel로 이미 결정된 접속 요청을 공유해요.
133
+
134
+ 파일을 불러오는 순간부터 console, 처리되지 않은 오류, 네트워크(XHR, fetch, WebSocket, sendBeacon, EventSource)를 종류별로 최대 500개까지 수집해요. 가득 차면 일반 로그와 성공한 요청부터 버려요. 응답 본문은 합계 5 MB, 하나당 64 KB까지 캐시해요. 그러니 `<head>`에서 동기적으로, 최대한 일찍 불러오세요.
135
+
136
+ ## 세션 로그 (프로토콜 §5.4)
137
+
138
+ 위의 버퍼는 한 페이지의 메모리일 뿐이에요. 이와 별도로 SDK는 기기의 IndexedDB(`rwd-log`)에 **세션 로그**를 기록해서, 버그가 난 뒤에 접속한 에이전트도 버그에 이르기까지의 과정을 처음부터 읽을 수 있어요:
139
+
140
+ - 세션 하나는 앱의 실행 한 번이고, 같은 출처의 모든 페이지가 공유해요. `run({ launchId })`, 없으면 `window.RWDLaunchId`, 그것도 없으면 세션 쿠키 `rwd_launch`와 하트비트로 추정해요 (쿠키 세션이 한 시간 넘게 조용하면 새 실행으로 봐요). `status().logSession`이 사용 중인 id예요.
141
+ - 기록하는 것: 페이지 열기 / 페이지 내 이동 / 포그라운드·백그라운드 전환 / 페이지 떠나기, 사용자의 탭, 입력, 제출, 스크롤이 멈춘 위치, 에이전트의 상호작용 호출과 `rwd.evaluate`, 제어 상태 변경, 모든 레벨의 console, 끝난 모든 요청(실패한 요청은 민감 정보를 가린 헤더와 본문까지, 각 64 KB). 게이트웨이에 닿을 수 있든 없든 동작해요. 마스킹된 필드는 `••••`로 저장되고, `maskPatterns`는 기록을 쓸 때 적용돼요.
142
+ - 보관: 최근 3번의 실행, 실행당 10 MB. 넘으면 일반 로그와 성공한 요청부터 버려요. 저장소 할당량이 64 MB 미만이면 현재 실행만 보관하고, IndexedDB가 없으면 아무것도 보관하지 않아요.
143
+ - 에이전트는 `rwd.log`(`observe`)와 `rwd.logDetail`(`advanced`, 헤더와 본문용)로 읽어요. 그 밖에는 아무것도 기기를 떠나지 않아요.
144
+ - 끄기: 한 페이지는 `run({ record: false })`, 프로젝트 전체는 `policy.sessionLog: false`. 후자는 이 출처에 이미 저장된 로그도 지워요.
145
+
146
+ ## 구조
147
+
148
+ - `src/index.ts`: 공개 API, 세션, RPC 디스패치, 제어 상태.
149
+ - `src/transport.ts`: 페이지 WebSocket(네이티브 생성자 사용), 하트비트 타임아웃, 지수 백오프, 3번 연속 실패하면 60초마다 `POST /v1/presence`.
150
+ - `src/ids.ts`: deviceId / pageId / openerId.
151
+ - `src/launch.ts`: 이 페이지가 앱의 어느 실행에 속하는지 (`launchId`, `window.RWDLaunchId`, `rwd_launch` 쿠키).
152
+ - `src/pageWatch.ts`: `page` 메시지(url, title, visibility)와 타임라인 이벤트(페이지 내 이동, 사용자의 탭).
153
+ - `src/ui.ts`, `src/lock.ts`: Shadow DOM 안의 배지, 지원 요청 버튼, 잠금 오버레이, 글로우, 동작 강조 표시, 대화 상자. 에이전트가 접속해 있는 동안 사용자의 신뢰할 수 있는 입력을 막고, 연결된 지 얼마나 됐는지 보여 줘요.
154
+ - `src/approval.ts`: 접속 승인과 민감한 작업 확인 (페이지 대화 상자, 또는 `nativeDecide`를 통한 네이티브 호스트).
155
+ - `src/i18n.ts`, `src/locales/`, `src/localeLoader*.ts`: 언어 팩(언어별 UI, action, agent 문구), 선택과 대체, 필요할 때 불러오기. `scripts/locales.mjs`가 `dist/locales/<id>.js`를 빌드해요.
156
+ - 에이전트 엔진은 chobitsu 옆의 `packages/chobitsu/src/engine/rwd/`에 있어요: 스냅샷, `rwd.query`, 조작 가능 여부 검사, 합성 입력, 키보드, console / 네트워크 버퍼, 스크린샷, `rwd.storage`, IndexedDB 세션 로그(`coldlog.ts`), 오버레이 감지(`overlays.ts`), 권한 수준과 확인 규칙(`policy.ts`), 에이전트용 문구(`i18n.ts`). 스냅샷에 쓰는 Playwright 소스는 `packages/chobitsu/src/engine/vendor/playwright/`에 있어요. 빌드할 때 이것들을 소스에서 바로 `dist/sdk.js`로 번들링해요.
157
+
158
+ ## 빌드와 테스트
159
+
160
+ ```sh
161
+ cd sdk
162
+ npm ci
163
+ npm run build # dist/sdk.js (IIFE, 전역 RemoteWebviewDebugger, ES2017), dist/index.mjs + index.d.ts (ES 모듈)
164
+ npm run typecheck
165
+ npm test # Playwright: WebKit (iPhone 15) + Chromium (Pixel 7)
166
+ npm run gateway # 로컬 모의 게이트웨이: http://localhost:8787/demo/platform/?key=rwd_pk_demo
167
+ ```
168
+
169
+ 테스트는 `test/mock-gateway.mjs`를 써요. 이 모의 게이트웨이는 프로토콜의 페이지 쪽(welcome, 제어 상태, RPC, presence, 토큰, 종료 코드, 권한 수준, 원격 config, `agentLang`, 접속 승인)을 구현하고, `/v1/sdk.js`, `/v1/locales/` 아래의 언어 팩, 데모 쇼핑몰을 제공해요. 권한 수준은 직접 검사하지 않으므로, 테스트는 페이지 자체의 강제 동작을 검증해요. `test/e2e/`의 스펙은 따로 지정하지 않는 한 `zh-CN` 브라우저와 `agentLang: 'zh'`로 실행돼요. `i18n.spec.ts`는 영어를, `locales.spec.ts`는 나머지 언어 팩, 대체, 사용자 정의 언어 팩, 스타일, 오른쪽에서 왼쪽으로 쓰는 레이아웃을 다뤄요. 공존 테스트는 jsdelivr에서 eruda 3.4.3과 PageSpy 2.4.0을 불러오므로 인터넷 연결이 필요해요.
package/README.md CHANGED
@@ -1,3 +1,168 @@
1
- # Temporary Holding Version
1
+ English | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Deutsch](README.de.md) | [Português (Brasil)](README.pt-BR.md) | [Русский](README.ru.md) | [العربية](README.ar.md)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # RWD page SDK (`sdk/`)
4
+
5
+ One JS file that an App WebView or H5 page loads, so an AI agent can inspect and operate the page through the RWD gateway (`gateway/`) and MCP. Protocol: [`docs/protocol.md`](../docs/protocol.md); architecture: [`docs/architecture.md`](../docs/architecture.md).
6
+
7
+ ```html
8
+ <script src="https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.2.0/dist/sdk.js"></script>
9
+ <script>
10
+ RemoteWebviewDebugger.run("https://rwd.example.com/v1", "rwd_pk_xxxxxxxx", {
11
+ userId: "u-42", // optional
12
+ deviceId: "…", // optional; passed in by a native host that injects the SDK
13
+ helpButton: true, // optional; show the "Request assistance" button
14
+ app: { name: "Shop", version: "3.2.0" },
15
+ lang: "en", // optional; a language tag, default from the browser's languages
16
+ }).then(({ deviceCode, status }) => console.log(deviceCode, status))
17
+ </script>
18
+ ```
19
+
20
+ ## Getting the SDK
21
+
22
+ Published to npm as [`remote-webview-debugger`](https://www.npmjs.com/package/remote-webview-debugger); versions and changes: [CHANGELOG](CHANGELOG.md).
23
+
24
+ | Use | Where |
25
+ |---|---|
26
+ | `<script>` from a CDN | `https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.2.0/dist/sdk.js` or `https://unpkg.com/remote-webview-debugger@0.2.0/dist/sdk.js` (minified IIFE, global `RemoteWebviewDebugger`; pin the version, and add `integrity` if you like) |
27
+ | A bundler | `npm install remote-webview-debugger`, then `import { run, status } from 'remote-webview-debugger'` (ES module with types). Import it before your own code so capture starts early. Don't also load the `<script>` version on the same page |
28
+ | Your gateway | `https://<gateway>/v1/sdk.js`, the copy the gateway embeds (also at a content-addressed URL for SRI, offered by the console) |
29
+ | A native app | Bundle `dist/sdk.js` from the package, or download it from one of the above; see [native integration](../docs/native-integration.md) |
30
+
31
+ ## API
32
+
33
+ | API | Description |
34
+ |---|---|
35
+ | `run(gatewayUrl, appKey, opts?)` | Bring the page online (device code, pill). Returns `Promise<{deviceCode, status}>`; calling it again is harmless. Options below. |
36
+ | `status()` | `{deviceCode, status, control, help, label, deviceId, pageId, version, lang, deviceToken, level, config, pending, logSession}`; `status` is `idle / connecting / online / polling / stopped / rejected / quota`, `lang` the UI language (`null` before `run()`), `logSession` the session log this page writes to (`null` when not recording) |
37
+ | `stop()` | Disconnect and remove the UI (console / network collection continues, bounded) |
38
+ | `setUser(userId)` | Change the user; reconnects once with a new `hello` |
39
+ | `help(on)` | Request assistance / cancel the request |
40
+ | `nativeControl(nativeKey, action)` | Take over / hand back / end from the native host's own UI |
41
+ | `nativeDecide(nativeKey, requestId, value)` | With `nativeApproval`, answer `status().pending`: a level or `null` for an attach request, `'allow'` or `null` for a sensitive-action confirmation |
42
+ | `setLang(lang?)` | Switch the UI language at runtime (an app's own language setting); no argument goes back to the browser's languages. See [Languages](#languages) |
43
+ | `registerLocale(pack)` | Add a language pack, or replace one with the same `id` (also takes an array). See [Languages](#languages) |
44
+ | `builtinLocales` | The ids of the built-in packs |
45
+
46
+ `run()` options:
47
+
48
+ | Option | Description |
49
+ |---|---|
50
+ | `userId` | The signed-in user, sent with the `hello` |
51
+ | `deviceId` | A stable device id from a native host (otherwise one is kept in localStorage) |
52
+ | `helpButton` | Show the "Request assistance" button (OR-ed with the project policy) |
53
+ | `app` | `{ name, version }` of the host app |
54
+ | `lang` | Language of everything the phone user sees (pill, floating window, bottom bar, dialogs, toasts, the eruda tab, and the "AI wants to …" description of a sensitive action): a language tag such as `'ja'` or `'pt-BR'`. Default: the browser's languages. See [Languages](#languages) |
55
+ | `locales` | Extra language packs, registered before the language is picked (like `registerLocale`) |
56
+ | `messages` | Messages to override, per language: `{ en: { ui: { help: 'Contact support' } }, zh: { ui: { help: '联系客服' } } }` |
57
+ | `networkExclude` | Requests the agent should not have to wade through — fonts that always 404, analytics beacons: `['.ttf', 'WebTracks', '*beacon*', '/\\/track\\//']`. Each rule matches the URL as a case-insensitive substring, as a `*` wildcard pattern, or as a regex when written `/…/`. Added to the project policy's rules. Matching requests stay out of the network list and out of `net_fail` timeline events; the page still records them, so an agent can ask for them with `includeExcluded` |
58
+ | `lockScreen` | `false`: don't lock the page while an agent is attached (the user can still change it in the floating window). Default `true` |
59
+ | `theme` | `'auto'` (default, follows the page background), `'system'`, `'light'` or `'dark'` |
60
+ | `style` | Colors, fonts, radii, z-index and positions of the UI: `{ accent: '#e11d48', radius: 12, zIndex: 1000, pill: { side: 'left' } }`. See [Styling](#styling) |
61
+ | `eruda` | `true` or `{ dock, load, src, name }`: show Remote Debugger as a tab inside eruda; `load` loads the latest eruda when the page has none. Off by default; the project policy can turn it on remotely, local options win field by field |
62
+ | `record` | `false`: this page writes no session log (and nothing already stored is deleted). Default `true`; the project policy can turn the log off for every page |
63
+ | `launchId` | Which launch of the app this page belongs to, so every WebView of one app process files its session log under one session. A native host passes its own id here (or defines `window.RWDLaunchId` as a string or a function returning one); otherwise the SDK infers the launch from a session cookie plus a heartbeat |
64
+ | `nativeKey`, `deviceToken`, `nativeApproval` | For native hosts: the secret for `nativeControl` / `nativeDecide`, the device token the host keeps, and asking the attach / confirm questions natively |
65
+
66
+ ## Languages
67
+
68
+ Built in: English (`en`), Simplified Chinese (`zh`), Traditional Chinese (`zh-TW`, also used for `zh-HK`, `zh-MO` and `zh-Hant`), French (`fr`), Spanish (`es`), Russian (`ru`), Arabic (`ar`, laid out right to left), Japanese (`ja`), Korean (`ko`), German (`de`) and Portuguese (`pt`, Brazilian; also used for `pt-PT`).
69
+
70
+ **Picking the language.** The phone user's language is `run({ lang })` if given, then each of `navigator.languages` in order. For each tag the SDK tries the tag and then shorter forms of it (`pt-BR` → `pt`, `zh-HK` → `zh-TW` → `zh`), takes the first language it has a pack for, and falls back to English. Messages missing from a pack fall back the same way, so a partial pack is fine. `setLang('ja')` switches at runtime; `status().lang` is the pack in use.
71
+
72
+ **Loading.** English and Simplified Chinese are inside `sdk.js`. The other packs load on demand, the first time a page needs them (the UI shows the fallback language for that moment):
73
+
74
+ - `<script>` build: from `locales/<id>.js` next to `sdk.js` (the CDN path, e.g. `…/remote-webview-debugger@0.2.0/dist/locales/ja.js`, or your gateway's `/v1/locales/ja.js`), and from the gateway passed to `run()` if that fails. To avoid the extra request, load the pack yourself, before or after `sdk.js`: `<script src="…/dist/locales/ja.js"></script>`.
75
+ - ES module build: a dynamic import, so your bundler puts each language in its own chunk. To bundle one up front instead: `import ja from 'remote-webview-debugger/locales/ja'`, then `run(url, key, { locales: [ja] })`.
76
+
77
+ **Your own packs and wording.** A pack is `{ id, name, dir?, ui?, action?, agent? }`; anything left out comes from its fallback languages:
78
+
79
+ ```js
80
+ RemoteWebviewDebugger.registerLocale({
81
+ id: 'it',
82
+ name: 'Italiano',
83
+ ui: { takeover: 'Prendi il controllo', end: 'Termina', help: 'Richiedi assistenza' /* … */ },
84
+ })
85
+ RemoteWebviewDebugger.run(url, key, {
86
+ messages: { en: { ui: { help: 'Contact support' } } }, // change single messages of any language
87
+ })
88
+ ```
89
+
90
+ `ui` is the UI text, `action` completes "AI wants to …" in the confirmation dialog, and `agent` is what goes back to the agent. The TypeScript types (`LocalePack`, `Messages`, `UiMessages`, `ActionMessages`, `AgentMessages`) list every key; `src/locales/en.ts` is the reference.
91
+
92
+ **Two audiences.** The phone user gets the language above. The agent reads RPC error messages and timeline summaries (taps, in-page navigation, failed requests) in the gateway's language, `welcome.agentLang` (`RWD_LANG`), loading that pack the same way. English until a welcome says otherwise; the last value is kept in localStorage `rwd:alang`, so events from before the next page's welcome use it too.
93
+
94
+ So a Chinese-speaking user can work with an English-speaking agent: the dialog asks `AI 想要点击「提交订单」,允许吗?` and, if refused, the agent reads `The user denied this action`.
95
+
96
+ ## Styling
97
+
98
+ The UI lives in a shadow root on `#__rwd` (outside `<body>`), so page CSS doesn't reach it except through these hooks:
99
+
100
+ - **CSS custom properties** on `:root` or `#__rwd`, e.g. `:root { --rwd-accent-rgb: 225, 29, 72; --rwd-radius: 12px; }`.
101
+ - **`run({ style })`**, which sets the same properties on `#__rwd`. Colors take any CSS color there.
102
+ - **`::part()`** for anything else: `#__rwd::part(bar) { … }`. Parts: `pill`, `window`, `bar`, `task`, `dialog`, `toast`, `button`, `cursor`, `glow`, `overlay`. Class names inside are not a stable API.
103
+ - **`theme`**: light or dark glass (default: follows the page background).
104
+
105
+ | Property | `style` field | Default | What |
106
+ |---|---|---|---|
107
+ | `--rwd-accent-rgb` | `accent` | `59, 130, 246` | Buttons, switches, the agent's pointer, highlights and the edge glow |
108
+ | `--rwd-danger-rgb` | `danger` | `239, 68, 68` | The End button |
109
+ | `--rwd-warning-rgb` | `warning` | `245, 158, 11` | Request assistance and Assisting; the user-in-control state |
110
+ | `--rwd-success-rgb` | `success` | `34, 197, 94` | Online dot; "copied" |
111
+ | `--rwd-font` | `font` | system UI font (per language for Chinese, Japanese, Korean) | All text |
112
+ | `--rwd-font-mono` | `monoFont` | `ui-monospace, "SF Mono", …` | Device code and tags |
113
+ | `--rwd-radius` | `radius` | `18px` | Floating window; the dialog is 2px rounder, the task card and pill 2px less, dialog buttons 6px less |
114
+ | `--rwd-button-radius` | `buttonRadius` | `999px` | Buttons, the bottom bar and toasts |
115
+ | `--rwd-z-index` | `zIndex` | `2147483647` | The whole UI |
116
+ | `--rwd-bar-bottom` | `barBottom` | `14px` | Bottom bar, above the bottom safe area |
117
+ | `--rwd-task-top` | `taskTop` | `10px` | Task card, below the top safe area |
118
+ | `--rwd-toast-bottom` | `toastBottom` | `84px` | Toasts, above the bottom safe area |
119
+ | — | `pill: { side, top }` | right edge (left in right-to-left languages), `0.62` | Where the idle pill starts until the user drags it; `top` from 0 (top) to 1 (bottom) |
120
+
121
+ Colors are `r, g, b` triplets so that the translucent shades follow (the same convention as Bootstrap's `--bs-*-rgb`).
122
+
123
+ ## Behavior in the page (protocol §6–§8, §11)
124
+
125
+ - Levels: RPCs are checked against `welcome.level` / `control.level` (`forbidden`, "Requires the … level"). An agent without a level (an older gateway) gets `interact`; with no agent attached nothing extra is enforced.
126
+ - The device token is kept in localStorage `rwd:dtoken`, the resume token in sessionStorage `rwd:resume` (bound to the pageId). Close code 4401: if the token changed meanwhile (another tab of the device just got it), retry once right away, else `rejected` and retry after about 10 minutes; 4409: reconnect with a new pageId; 4429: `quota`, retry after about 5 minutes.
127
+ - `rwd_invite` in the address bar is taken out at `run()` (`history.replaceState`) and sent with the hello until a welcome arrives.
128
+ - `welcome.config`: `helpButton` (OR-ed with the `run()` option), `maskSelectors`, `maskPatterns` (snapshot, console, network URLs, storage values, timeline summaries), `confirmSelectors`, `networkExclude` (added to the `run()` option's rules), `sessionLog`. The last config is kept in localStorage `rwd:cfg` and used until the welcome arrives — so a project that turned the session log off keeps it off from the first moment of the next page, before any welcome.
129
+ - Snapshots end with an `[Overlays]` section listing what floats over the page — a popup's backdrop, a modal, a bottom sheet — with its tag, a short piece of text, how much of the viewport it covers and a ref. A backdrop is an empty element the aria snapshot has no node for, so it gets an `oN` ref instead of `eN`; that is what lets an agent tap it to close the popup.
130
+ - A click aims at the element's visible centre first, then at the other spots of a 3×3 grid inside it, so a badge over a button doesn't block it. When every spot is covered, the RPC fails with `not_interactable` and names the obscuring element and its ref.
131
+ - The attach and sensitive-action dialogs (`[data-rwd-confirm]`, `confirmSelectors`, including Enter submitting a form) live in a Shadow DOM, accept trusted taps only, and arm 0.4 s after they appear. Tabs of the same origin share decided attach requests over BroadcastChannel.
132
+
133
+ Loading the file starts collecting console, uncaught errors and network (XHR, fetch, WebSocket, sendBeacon, EventSource), at most 500 entries each; when full, plain logs and successful requests go first. Response bodies are cached up to 5 MB in total, 64 KB each. So load it synchronously and early in `<head>`.
134
+
135
+ ## Session log (protocol §5.4)
136
+
137
+ Those buffers are one page's memory. In parallel the SDK writes a **session log** to IndexedDB (`rwd-log`) on the device, so an agent that attaches after a bug can still read the whole way to it:
138
+
139
+ - One session is one launch of the app, shared by every page of the origin: `run({ launchId })`, else `window.RWDLaunchId`, else inferred from the session cookie `rwd_launch` plus a heartbeat (a cookie session silent for over an hour counts as a new launch). `status().logSession` is the id in use.
140
+ - Recorded: page open / in-page navigation / foreground-background / leave, the user's taps, input, submits and scroll rest positions, the agent's interactive calls and `rwd.evaluate`, control changes, console at every level, and every finished request — failed ones also with redacted headers and bodies (64 KB each). It runs whether or not the gateway is reachable. Masked fields are stored as `••••`, `maskPatterns` are applied as records are written.
141
+ - Kept: the last 3 launches, 10 MB each; over that, plain logs and successful requests are dropped first. Only the current launch when the storage quota is under 64 MB; nothing at all without IndexedDB.
142
+ - Read by the agent with `rwd.log` (`observe`) and `rwd.logDetail` (`advanced`, for headers and bodies). Nothing leaves the device otherwise.
143
+ - Off with `run({ record: false })` for one page, or `policy.sessionLog: false` for the project — which also deletes what this origin already stored.
144
+
145
+ ## Layout
146
+
147
+ - `src/index.ts`: public API, session, RPC dispatch, control state.
148
+ - `src/transport.ts`: the page WebSocket (the native constructor), heartbeat timeout, exponential backoff, and after 3 failed attempts `POST /v1/presence` every 60 s.
149
+ - `src/ids.ts`: deviceId / pageId / openerId.
150
+ - `src/launch.ts`: which launch of the app this page belongs to (`launchId`, `window.RWDLaunchId`, the `rwd_launch` cookie).
151
+ - `src/pageWatch.ts`: `page` messages (url, title, visibility) and timeline events (in-page navigation, the user's taps).
152
+ - `src/ui.ts`, `src/lock.ts`: the pill, Request assistance button, lock overlay, glow, action highlight and dialogs in a Shadow DOM; blocks the user's trusted input while an agent is attached, and shows how long it has been connected.
153
+ - `src/approval.ts`: attach approval and sensitive-action confirmation (page dialog, or the native host via `nativeDecide`).
154
+ - `src/i18n.ts`, `src/locales/`, `src/localeLoader*.ts`: language packs (UI, action and agent text per language), picking and fallback, on-demand loading; `scripts/locales.mjs` builds `dist/locales/<id>.js`.
155
+ - The agent engine lives next to chobitsu in `packages/chobitsu/src/engine/rwd/`: snapshot, `rwd.query`, actionability checks, synthesized input, keyboard, console / network buffers, screenshots, `rwd.storage`, the session log in IndexedDB (`coldlog.ts`), overlay detection (`overlays.ts`), levels and confirmation rules (`policy.ts`), agent-facing text (`i18n.ts`). The Playwright sources used for snapshots are in `packages/chobitsu/src/engine/vendor/playwright/`. The build bundles them from source into `dist/sdk.js`.
156
+
157
+ ## Build and test
158
+
159
+ ```sh
160
+ cd sdk
161
+ npm ci
162
+ npm run build # dist/sdk.js (IIFE, global RemoteWebviewDebugger, ES2017), dist/index.mjs + index.d.ts (ES module)
163
+ npm run typecheck
164
+ npm test # Playwright: WebKit (iPhone 15) + Chromium (Pixel 7)
165
+ npm run gateway # local mock gateway: http://localhost:8787/demo/platform/?key=rwd_pk_demo
166
+ ```
167
+
168
+ The tests use `test/mock-gateway.mjs`, which implements the page side of the protocol (welcome, control state, RPCs, presence; tokens, close codes, levels, remote config, `agentLang`, attach approval) and serves `/v1/sdk.js`, the language packs under `/v1/locales/` and the demo shop. It does not check levels itself, so the tests exercise the page's own enforcement. The specs in `test/e2e/` run with a `zh-CN` browser and `agentLang: 'zh'` unless a test says otherwise; `i18n.spec.ts` covers English, `locales.spec.ts` the other language packs, fallback, custom packs, styling and the right-to-left layout. The coexistence test loads eruda 3.4.3 and PageSpy 2.4.0 from jsdelivr, so it needs internet access.
@@ -0,0 +1,169 @@
1
+ <!-- i18n-source: d9a31993ea867d3ef7058e7d637886f38a6ee3e1 -->
2
+ [English](README.md) | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [Español](README.es.md) | [Français](README.fr.md) | [Deutsch](README.de.md) | Português (Brasil) | [Русский](README.ru.md) | [العربية](README.ar.md)
3
+
4
+ # SDK da página do RWD (`sdk/`)
5
+
6
+ Um único arquivo JS que uma WebView de app ou uma página H5 carrega para que um agente de IA possa inspecionar e operar a página pelo gateway do RWD (`gateway/`) e pelo MCP. Protocolo: [`docs/protocol.pt-BR.md`](../docs/protocol.pt-BR.md); arquitetura: [`docs/architecture.pt-BR.md`](../docs/architecture.pt-BR.md).
7
+
8
+ ```html
9
+ <script src="https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.1.0/dist/sdk.js"></script>
10
+ <script>
11
+ RemoteWebviewDebugger.run("https://rwd.example.com/v1", "rwd_pk_xxxxxxxx", {
12
+ userId: "u-42", // opcional
13
+ deviceId: "…", // opcional; passado por um host nativo que injeta o SDK
14
+ helpButton: true, // opcional; mostra o botão "Solicitar assistência"
15
+ app: { name: "Shop", version: "3.2.0" },
16
+ lang: "en", // opcional; uma tag de idioma, por padrão vem dos idiomas do navegador
17
+ }).then(({ deviceCode, status }) => console.log(deviceCode, status))
18
+ </script>
19
+ ```
20
+
21
+ ## Como obter o SDK
22
+
23
+ Publicado no npm como [`remote-webview-debugger`](https://www.npmjs.com/package/remote-webview-debugger); versões e mudanças: [CHANGELOG](CHANGELOG.md).
24
+
25
+ | Uso | Onde |
26
+ |---|---|
27
+ | `<script>` de uma CDN | `https://cdn.jsdelivr.net/npm/remote-webview-debugger@0.1.0/dist/sdk.js` ou `https://unpkg.com/remote-webview-debugger@0.1.0/dist/sdk.js` (IIFE minificado, global `RemoteWebviewDebugger`; fixe a versão e, se quiser, adicione `integrity`) |
28
+ | Um bundler | `npm install remote-webview-debugger` e depois `import { run, status } from 'remote-webview-debugger'` (módulo ES com tipos). Importe-o antes do seu próprio código para que a captura comece cedo. Não carregue também a versão `<script>` na mesma página |
29
+ | O seu gateway | `https://<gateway>/v1/sdk.js`, a cópia embutida no gateway (também disponível numa URL endereçada por conteúdo para SRI, oferecida pelo console) |
30
+ | Um app nativo | Empacote o `dist/sdk.js` do pacote ou baixe-o de uma das opções acima; veja [integração nativa](../docs/native-integration.pt-BR.md) |
31
+
32
+ ## API
33
+
34
+ | API | Descrição |
35
+ |---|---|
36
+ | `run(gatewayUrl, appKey, opts?)` | Coloca a página online (código do dispositivo, pílula). Retorna `Promise<{deviceCode, status}>`; chamar de novo não tem efeito colateral. Opções abaixo. |
37
+ | `status()` | `{deviceCode, status, control, help, label, deviceId, pageId, version, lang, deviceToken, level, config, pending, logSession}`; `status` é `idle / connecting / online / polling / stopped / rejected / quota`, `lang` é o idioma da interface (`null` antes de `run()`), `logSession` é o log da sessão em que esta página grava (`null` quando não está gravando) |
38
+ | `stop()` | Desconecta e remove a interface (a coleta de console / rede continua, com limite) |
39
+ | `setUser(userId)` | Troca o usuário; reconecta uma vez com um novo `hello` |
40
+ | `help(on)` | Solicita assistência / cancela a solicitação |
41
+ | `nativeControl(nativeKey, action)` | Assumir o controle / devolver o controle / encerrar a partir da interface do próprio host nativo |
42
+ | `nativeDecide(nativeKey, requestId, value)` | Com `nativeApproval`, responde a `status().pending`: um nível ou `null` para um pedido de conexão, `'allow'` ou `null` para a confirmação de uma ação sensível |
43
+ | `setLang(lang?)` | Troca o idioma da interface em tempo de execução (a configuração de idioma do próprio app); sem argumento, volta aos idiomas do navegador. Veja [Idiomas](#idiomas) |
44
+ | `registerLocale(pack)` | Adiciona um pacote de idioma, ou substitui um com o mesmo `id` (também aceita um array). Veja [Idiomas](#idiomas) |
45
+ | `builtinLocales` | Os ids dos pacotes embutidos |
46
+
47
+ Opções de `run()`:
48
+
49
+ | Opção | Descrição |
50
+ |---|---|
51
+ | `userId` | O usuário logado, enviado com o `hello` |
52
+ | `deviceId` | Um id de dispositivo estável vindo de um host nativo (senão, um é guardado no localStorage) |
53
+ | `helpButton` | Mostra o botão "Solicitar assistência" (combinado com OR com a política do projeto) |
54
+ | `app` | `{ name, version }` do app host |
55
+ | `lang` | Idioma de tudo o que o usuário do celular vê (pílula, janela flutuante, barra inferior, diálogos, toasts, a aba do eruda e a descrição "A IA quer …" de uma ação sensível): uma tag de idioma como `'ja'` ou `'pt-BR'`. Padrão: os idiomas do navegador. Veja [Idiomas](#idiomas) |
56
+ | `locales` | Pacotes de idioma extras, registrados antes de o idioma ser escolhido (como `registerLocale`) |
57
+ | `messages` | Mensagens a sobrescrever, por idioma: `{ en: { ui: { help: 'Contact support' } }, zh: { ui: { help: '联系客服' } } }` |
58
+ | `networkExclude` | Requisições em que o agente não deveria ter que se atolar — fontes que sempre dão 404, beacons de analytics: `['.ttf', 'WebTracks', '*beacon*', '/\\/track\\//']`. Cada regra casa com a URL como substring sem diferenciar maiúsculas de minúsculas, como padrão com curinga `*` ou como regex quando escrita `/…/`. Somadas às regras da política do projeto. As requisições que casam ficam fora da lista de rede e fora dos eventos `net_fail` da linha do tempo; a página continua registrando-as, então um agente pode pedi-las com `includeExcluded` |
59
+ | `lockScreen` | `false`: não bloqueia a página enquanto um agente estiver conectado (o usuário ainda pode mudar isso na janela flutuante). Padrão `true` |
60
+ | `theme` | `'auto'` (padrão, segue o fundo da página), `'system'`, `'light'` ou `'dark'` |
61
+ | `style` | Cores, fontes, raios, z-index e posições da interface: `{ accent: '#e11d48', radius: 12, zIndex: 1000, pill: { side: 'left' } }`. Veja [Estilos](#estilos) |
62
+ | `eruda` | `true` ou `{ dock, load, src, name }`: mostra o Remote Debugger como uma aba dentro do eruda; `load` carrega o eruda mais recente quando a página não tem um. Desligado por padrão; a política do projeto pode ligá-lo remotamente, e as opções locais prevalecem campo a campo |
63
+ | `record` | `false`: esta página não grava log da sessão (e nada do que já está armazenado é apagado). Padrão `true`; a política do projeto pode desligar o log para todas as páginas |
64
+ | `launchId` | A qual inicialização do app esta página pertence, para que todas as WebViews de um mesmo processo do app arquivem o log da sessão numa única sessão. Um host nativo passa o próprio id aqui (ou define `window.RWDLaunchId` como uma string ou uma função que retorna uma); caso contrário, o SDK deduz a inicialização a partir de um cookie de sessão mais um heartbeat |
65
+ | `nativeKey`, `deviceToken`, `nativeApproval` | Para hosts nativos: o segredo para `nativeControl` / `nativeDecide`, o token do dispositivo que o host guarda, e fazer as perguntas de conexão / confirmação de forma nativa |
66
+
67
+ ## Idiomas
68
+
69
+ Embutidos: inglês (`en`), chinês simplificado (`zh`), chinês tradicional (`zh-TW`, usado também para `zh-HK`, `zh-MO` e `zh-Hant`), francês (`fr`), espanhol (`es`), russo (`ru`), árabe (`ar`, com layout da direita para a esquerda), japonês (`ja`), coreano (`ko`), alemão (`de`) e português (`pt`, do Brasil; usado também para `pt-PT`).
70
+
71
+ **Como o idioma é escolhido.** O idioma do usuário do celular é `run({ lang })`, se informado, e depois cada um dos `navigator.languages`, em ordem. Para cada tag, o SDK tenta a própria tag e depois formas mais curtas dela (`pt-BR` → `pt`, `zh-HK` → `zh-TW` → `zh`), fica com o primeiro idioma para o qual tem pacote e, se não houver nenhum, usa o inglês. As mensagens que faltam num pacote caem nos idiomas de fallback da mesma forma, então um pacote parcial não é problema. `setLang('ja')` troca o idioma em tempo de execução; `status().lang` é o pacote em uso.
72
+
73
+ **Carregamento.** Inglês e chinês simplificado vêm dentro do `sdk.js`. Os demais pacotes são carregados sob demanda, na primeira vez que uma página precisa deles (nesse instante, a interface mostra o idioma de fallback):
74
+
75
+ - Build `<script>`: a partir de `locales/<id>.js` ao lado do `sdk.js` (o caminho da CDN, por exemplo `…/remote-webview-debugger@0.1.0/dist/locales/ja.js`, ou `/v1/locales/ja.js` do seu gateway) e, se isso falhar, a partir do gateway passado para `run()`. Para evitar a requisição extra, carregue você mesmo o pacote, antes ou depois do `sdk.js`: `<script src="…/dist/locales/ja.js"></script>`.
76
+ - Build de módulo ES: um import dinâmico, então o seu bundler coloca cada idioma no próprio chunk. Para incluir um idioma no bundle desde o início: `import ja from 'remote-webview-debugger/locales/ja'` e depois `run(url, key, { locales: [ja] })`.
77
+
78
+ **Seus próprios pacotes e textos.** Um pacote é `{ id, name, dir?, ui?, action?, agent? }`; o que ficar de fora vem dos idiomas de fallback dele:
79
+
80
+ ```js
81
+ RemoteWebviewDebugger.registerLocale({
82
+ id: 'it',
83
+ name: 'Italiano',
84
+ ui: { takeover: 'Prendi il controllo', end: 'Termina', help: 'Richiedi assistenza' /* … */ },
85
+ })
86
+ RemoteWebviewDebugger.run(url, key, {
87
+ messages: { en: { ui: { help: 'Contact support' } } }, // altera mensagens avulsas de qualquer idioma
88
+ })
89
+ ```
90
+
91
+ `ui` é o texto da interface, `action` completa "A IA quer …" no diálogo de confirmação e `agent` é o que volta para o agente. Os tipos de TypeScript (`LocalePack`, `Messages`, `UiMessages`, `ActionMessages`, `AgentMessages`) listam todas as chaves; `src/locales/en.ts` é a referência.
92
+
93
+ **Dois públicos.** O usuário do celular recebe o idioma descrito acima. O agente lê as mensagens de erro de RPC e os resumos da linha do tempo (toques, navegação dentro da página, requisições com falha) no idioma do gateway, `welcome.agentLang` (`RWD_LANG`), e esse pacote é carregado da mesma forma. Fica em inglês até que um welcome diga outra coisa; o último valor é guardado no localStorage `rwd:alang`, então os eventos anteriores ao welcome da próxima página também o usam.
94
+
95
+ Assim, um usuário que fala chinês pode trabalhar com um agente que fala inglês: o diálogo pergunta `AI 想要点击「提交订单」,允许吗?` e, se a ação for recusada, o agente lê `The user denied this action`.
96
+
97
+ ## Estilos
98
+
99
+ A interface fica num shadow root em `#__rwd` (fora do `<body>`), então o CSS da página não a alcança, exceto por estes ganchos:
100
+
101
+ - **CSS custom properties** em `:root` ou `#__rwd`, por exemplo `:root { --rwd-accent-rgb: 225, 29, 72; --rwd-radius: 12px; }`.
102
+ - **`run({ style })`**, que define as mesmas propriedades em `#__rwd`. Ali, as cores aceitam qualquer cor CSS.
103
+ - **`::part()`** para todo o resto: `#__rwd::part(bar) { … }`. Parts: `pill`, `window`, `bar`, `task`, `dialog`, `toast`, `button`, `cursor`, `glow`, `overlay`. Os nomes de classe internos não são uma API estável.
104
+ - **`theme`**: vidro claro ou escuro (padrão: segue o fundo da página).
105
+
106
+ | Propriedade | Campo de `style` | Padrão | O quê |
107
+ |---|---|---|---|
108
+ | `--rwd-accent-rgb` | `accent` | `59, 130, 246` | Botões, chaves, o ponteiro do agente, destaques e o brilho nas bordas |
109
+ | `--rwd-danger-rgb` | `danger` | `239, 68, 68` | O botão Encerrar |
110
+ | `--rwd-warning-rgb` | `warning` | `245, 158, 11` | Solicitar assistência e Em atendimento; o estado de usuário no controle |
111
+ | `--rwd-success-rgb` | `success` | `34, 197, 94` | Ponto de online; "copiado" |
112
+ | `--rwd-font` | `font` | fonte de interface do sistema (por idioma para chinês, japonês e coreano) | Todo o texto |
113
+ | `--rwd-font-mono` | `monoFont` | `ui-monospace, "SF Mono", …` | Código do dispositivo e tags |
114
+ | `--rwd-radius` | `radius` | `18px` | Janela flutuante; o diálogo é 2px mais arredondado, o cartão de tarefa e a pílula 2px menos, os botões do diálogo 6px menos |
115
+ | `--rwd-button-radius` | `buttonRadius` | `999px` | Botões, a barra inferior e os toasts |
116
+ | `--rwd-z-index` | `zIndex` | `2147483647` | A interface inteira |
117
+ | `--rwd-bar-bottom` | `barBottom` | `14px` | Barra inferior, acima da área segura inferior |
118
+ | `--rwd-task-top` | `taskTop` | `10px` | Cartão de tarefa, abaixo da área segura superior |
119
+ | `--rwd-toast-bottom` | `toastBottom` | `84px` | Toasts, acima da área segura inferior |
120
+ | — | `pill: { side, top }` | borda direita (esquerda nos idiomas da direita para a esquerda), `0.62` | Onde a pílula ociosa começa até o usuário arrastá-la; `top` vai de 0 (topo) a 1 (base) |
121
+
122
+ As cores são trios `r, g, b` para que os tons translúcidos acompanhem (a mesma convenção do `--bs-*-rgb` do Bootstrap).
123
+
124
+ ## Comportamento na página (protocolo §6–§8, §11)
125
+
126
+ - Níveis: as RPCs são verificadas contra `welcome.level` / `control.level` (`forbidden`, "Requer o nível …"). Um agente sem nível (um gateway mais antigo) recebe `interact`; sem agente conectado, nada extra é aplicado.
127
+ - O token do dispositivo fica no localStorage `rwd:dtoken`, o token de retomada no sessionStorage `rwd:resume` (vinculado ao pageId). Código de fechamento 4401: se o token mudou nesse meio-tempo (outra aba do dispositivo acabou de recebê-lo), tenta de novo uma vez na hora; senão, `rejected` e nova tentativa depois de uns 10 minutos; 4409: reconecta com um novo pageId; 4429: `quota`, nova tentativa depois de uns 5 minutos.
128
+ - `rwd_invite` na barra de endereço é retirado em `run()` (`history.replaceState`) e enviado com o hello até chegar um welcome.
129
+ - `welcome.config`: `helpButton` (combinado com OR com a opção de `run()`), `maskSelectors`, `maskPatterns` (snapshot, console, URLs de rede, valores de armazenamento, resumos da linha do tempo), `confirmSelectors`, `networkExclude` (somado às regras da opção de `run()`), `sessionLog`. A última config é guardada no localStorage `rwd:cfg` e usada até o welcome chegar — assim, um projeto que desligou o log da sessão o mantém desligado desde o primeiro instante da próxima página, antes de qualquer welcome.
130
+ - Os snapshots terminam com uma seção `[Overlays]` que lista o que flutua sobre a página — o fundo de um pop-up, um modal, um bottom sheet — com a tag, um trecho curto de texto, quanto da viewport cobre e uma ref. Um fundo é um elemento vazio que não tem nó no snapshot ARIA, então recebe uma ref `oN` em vez de `eN`; é isso que permite a um agente tocar nele para fechar o pop-up.
131
+ - Um clique mira primeiro o centro visível do elemento e depois os outros pontos de uma grade 3×3 dentro dele, para que um selo sobre um botão não o bloqueie. Quando todos os pontos estão cobertos, a RPC falha com `not_interactable` e informa o elemento que está cobrindo e a ref dele.
132
+ - Os diálogos de conexão e de ação sensível (`[data-rwd-confirm]`, `confirmSelectors`, incluindo Enter enviando um formulário) ficam num Shadow DOM, aceitam só toques confiáveis (trusted) e só ficam ativos 0,4 s depois de aparecerem. Abas da mesma origem compartilham os pedidos de conexão já decididos via BroadcastChannel.
133
+
134
+ Carregar o arquivo já começa a coletar console, erros não capturados e rede (XHR, fetch, WebSocket, sendBeacon, EventSource), no máximo 500 entradas de cada; quando o buffer enche, logs simples e requisições bem-sucedidas saem primeiro. Os corpos de resposta ficam em cache até 5 MB no total, 64 KB cada. Por isso, carregue-o de forma síncrona e cedo no `<head>`.
135
+
136
+ ## Log da sessão (protocolo §5.4)
137
+
138
+ Esses buffers são a memória de uma única página. Em paralelo, o SDK grava um **log da sessão** no IndexedDB (`rwd-log`) do dispositivo, para que um agente que se conecte depois de um bug ainda consiga ler todo o caminho até ele:
139
+
140
+ - Uma sessão é uma inicialização do app, compartilhada por todas as páginas da origem: `run({ launchId })`, senão `window.RWDLaunchId`, senão deduzida a partir do cookie de sessão `rwd_launch` mais um heartbeat (uma sessão de cookie sem sinal por mais de uma hora conta como uma nova inicialização). `status().logSession` é o id em uso.
141
+ - O que é gravado: abertura de página / navegação dentro da página / primeiro plano e segundo plano / saída, os toques do usuário, entradas de texto, envios de formulário e posições em que a rolagem parou, as chamadas interativas do agente e `rwd.evaluate`, mudanças de controle, console em todos os níveis e todas as requisições concluídas — as que falharam também com cabeçalhos e corpos redigidos (64 KB cada). Funciona esteja o gateway acessível ou não. Os campos mascarados são guardados como `••••`, e os `maskPatterns` são aplicados à medida que os registros são gravados.
142
+ - O que é mantido: as 3 últimas inicializações, 10 MB cada; acima disso, logs simples e requisições bem-sucedidas são descartados primeiro. Só a inicialização atual quando a cota de armazenamento é menor que 64 MB; nada sem IndexedDB.
143
+ - Lido pelo agente com `rwd.log` (`observe`) e `rwd.logDetail` (`advanced`, para cabeçalhos e corpos). Fora isso, nada sai do dispositivo.
144
+ - Desligado com `run({ record: false })` para uma página, ou `policy.sessionLog: false` para o projeto — o que também apaga o que esta origem já tinha armazenado.
145
+
146
+ ## Estrutura do código
147
+
148
+ - `src/index.ts`: API pública, sessão, despacho de RPC, estado de controle.
149
+ - `src/transport.ts`: o WebSocket da página (o construtor nativo), timeout de heartbeat, backoff exponencial e, depois de 3 tentativas falhas, `POST /v1/presence` a cada 60 s.
150
+ - `src/ids.ts`: deviceId / pageId / openerId.
151
+ - `src/launch.ts`: a qual inicialização do app esta página pertence (`launchId`, `window.RWDLaunchId`, o cookie `rwd_launch`).
152
+ - `src/pageWatch.ts`: mensagens `page` (url, title, visibility) e eventos da linha do tempo (navegação dentro da página, toques do usuário).
153
+ - `src/ui.ts`, `src/lock.ts`: a pílula, o botão “Solicitar assistência”, a camada de bloqueio, o brilho, o destaque de ação e os diálogos num Shadow DOM; bloqueia as entradas confiáveis do usuário enquanto um agente está conectado e mostra há quanto tempo ele está conectado.
154
+ - `src/approval.ts`: aprovação de conexão e confirmação de ação sensível (diálogo na página, ou o host nativo via `nativeDecide`).
155
+ - `src/i18n.ts`, `src/locales/`, `src/localeLoader*.ts`: pacotes de idioma (textos de interface, de ação e do agente por idioma), escolha e fallback, carregamento sob demanda; `scripts/locales.mjs` gera `dist/locales/<id>.js`.
156
+ - O motor do agente fica ao lado do chobitsu, em `packages/chobitsu/src/engine/rwd/`: snapshot, `rwd.query`, verificações de acionabilidade, entrada sintetizada, teclado, buffers de console / rede, capturas de tela, `rwd.storage`, o log da sessão no IndexedDB (`coldlog.ts`), detecção de sobreposições (`overlays.ts`), níveis e regras de confirmação (`policy.ts`), textos voltados ao agente (`i18n.ts`). Os fontes do Playwright usados nos snapshots estão em `packages/chobitsu/src/engine/vendor/playwright/`. O build os empacota a partir do código-fonte em `dist/sdk.js`.
157
+
158
+ ## Build e testes
159
+
160
+ ```sh
161
+ cd sdk
162
+ npm ci
163
+ npm run build # dist/sdk.js (IIFE, global RemoteWebviewDebugger, ES2017), dist/index.mjs + index.d.ts (módulo ES)
164
+ npm run typecheck
165
+ npm test # Playwright: WebKit (iPhone 15) + Chromium (Pixel 7)
166
+ npm run gateway # gateway mock local: http://localhost:8787/demo/platform/?key=rwd_pk_demo
167
+ ```
168
+
169
+ Os testes usam `test/mock-gateway.mjs`, que implementa o lado da página do protocolo (welcome, estado de controle, RPCs, presença; tokens, códigos de fechamento, níveis, config remota, `agentLang`, aprovação de conexão) e serve `/v1/sdk.js`, os pacotes de idioma em `/v1/locales/` e a loja de demonstração. Ele não verifica os níveis por conta própria, então os testes exercitam a aplicação feita pela própria página. As specs em `test/e2e/` rodam com um navegador `zh-CN` e `agentLang: 'zh'`, a menos que o teste diga outra coisa; `i18n.spec.ts` cobre o inglês, `locales.spec.ts` os outros pacotes de idioma, o fallback, pacotes personalizados, estilos e o layout da direita para a esquerda. O teste de coexistência carrega o eruda 3.4.3 e o PageSpy 2.4.0 do jsdelivr, então precisa de acesso à internet.