@ahnseungchan/webviewkit 1.0.8 → 1.3.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 (67) hide show
  1. package/README.md +67 -107
  2. package/dist/bridge/WebViewBridgeProvider.d.ts +18 -0
  3. package/dist/bridge/WebViewBridgeProvider.d.ts.map +1 -0
  4. package/dist/bridge/WebViewBridgeProvider.js +26 -0
  5. package/dist/bridge/client.d.ts +20 -0
  6. package/dist/bridge/client.d.ts.map +1 -0
  7. package/dist/bridge/client.js +48 -0
  8. package/dist/bridge/detect-host.d.ts +4 -0
  9. package/dist/bridge/detect-host.d.ts.map +1 -0
  10. package/dist/bridge/detect-host.js +19 -0
  11. package/dist/bridge/host.d.ts +31 -0
  12. package/dist/bridge/host.d.ts.map +1 -0
  13. package/dist/bridge/host.js +77 -0
  14. package/dist/bridge/index.d.ts +10 -0
  15. package/dist/bridge/index.d.ts.map +1 -0
  16. package/dist/bridge/index.js +33 -0
  17. package/dist/bridge/protocol.d.ts +51 -0
  18. package/dist/bridge/protocol.d.ts.map +1 -0
  19. package/dist/bridge/protocol.js +65 -0
  20. package/dist/bridge/stack-router-bridge.d.ts +17 -0
  21. package/dist/bridge/stack-router-bridge.d.ts.map +1 -0
  22. package/dist/bridge/stack-router-bridge.js +38 -0
  23. package/dist/bridge/transport.d.ts +11 -0
  24. package/dist/bridge/transport.d.ts.map +1 -0
  25. package/dist/bridge/transport.js +65 -0
  26. package/dist/constants.d.ts +3 -0
  27. package/dist/constants.d.ts.map +1 -0
  28. package/dist/constants.js +5 -0
  29. package/dist/{packages/core/src/history.d.ts → history.d.ts} +3 -2
  30. package/dist/history.d.ts.map +1 -0
  31. package/dist/history.js +91 -0
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +46 -0
  35. package/dist/stack-dev/stack-dev-roadmap.d.ts +15 -0
  36. package/dist/stack-dev/stack-dev-roadmap.d.ts.map +1 -0
  37. package/dist/stack-dev/stack-dev-roadmap.js +66 -0
  38. package/dist/stack-router/_components/back-router.d.ts +3 -0
  39. package/dist/stack-router/_components/back-router.d.ts.map +1 -0
  40. package/dist/stack-router/_components/back-router.js +39 -0
  41. package/dist/stack-router/_components/header.d.ts +3 -0
  42. package/dist/stack-router/_components/header.d.ts.map +1 -0
  43. package/dist/stack-router/_components/header.js +42 -0
  44. package/dist/{packages/core/src/stack-router → stack-router}/stack-router-provider.d.ts +4 -5
  45. package/dist/stack-router/stack-router-provider.d.ts.map +1 -0
  46. package/dist/stack-router/stack-router-provider.js +227 -0
  47. package/dist/{packages/core/src/type.d.ts → type.d.ts} +3 -5
  48. package/dist/type.d.ts.map +1 -0
  49. package/dist/type.js +14 -0
  50. package/package.json +22 -26
  51. package/dist/packages/core/src/__tests__/back-router.test.d.ts +0 -0
  52. package/dist/packages/core/src/__tests__/back-router.test.js +0 -62
  53. package/dist/packages/core/src/__tests__/history.test.d.ts +0 -1
  54. package/dist/packages/core/src/__tests__/history.test.js +0 -114
  55. package/dist/packages/core/src/__tests__/stack-router.test.d.ts +0 -0
  56. package/dist/packages/core/src/__tests__/stack-router.test.js +0 -185
  57. package/dist/packages/core/src/history.js +0 -82
  58. package/dist/packages/core/src/index.d.ts +0 -5
  59. package/dist/packages/core/src/index.js +0 -6
  60. package/dist/packages/core/src/stack-router/__tests__/stack-router.test.d.ts +0 -0
  61. package/dist/packages/core/src/stack-router/__tests__/stack-router.test.js +0 -153
  62. package/dist/packages/core/src/stack-router/_components/back-router.d.ts +0 -2
  63. package/dist/packages/core/src/stack-router/_components/back-router.js +0 -34
  64. package/dist/packages/core/src/stack-router/_components/header.d.ts +0 -2
  65. package/dist/packages/core/src/stack-router/_components/header.js +0 -36
  66. package/dist/packages/core/src/stack-router/stack-router-provider.js +0 -211
  67. package/dist/packages/core/src/type.js +0 -11
package/README.md CHANGED
@@ -1,149 +1,109 @@
1
- # WebViewKit
1
+ # @ahnseungchan/webviewkit
2
2
 
3
- A modern React library for building stack-based navigation in WebView applications, providing smooth transitions and native-like navigation experience.
3
+ React **WebView·하이브리드 앱**용 스택(push/back) 네비게이션 라이브러리입니다.
4
+ `history.pushState`와 CSS 슬라이드 전환으로 네이티브 앱에 가까운 화면 이동을 구현합니다.
4
5
 
5
- ## Features
6
+ ## 설치
6
7
 
7
- - 🚀 **Stack-based Navigation**: Native-like navigation with push/pop transitions
8
- - 🎨 **Smooth Animations**: GSAP-powered animations for fluid page transitions
9
- - 📱 **Mobile-First**: Designed specifically for WebView and mobile applications
10
- - 🔧 **TypeScript Support**: Full TypeScript support with comprehensive type definitions
11
- - 🎯 **Flexible**: Customizable animations, layouts, and navigation behavior
8
+ ```bash
9
+ npm install @ahnseungchan/webviewkit
10
+ ```
12
11
 
13
- ## Installation
12
+ **Peer dependencies** (프로젝트에 직접 설치):
14
13
 
15
14
  ```bash
16
- npm install @webviewkit
17
- # or
18
- yarn add @webviewkit
19
- # or
20
- pnpm add @webviewkit
15
+ npm install react react-dom @emotion/react @emotion/styled
21
16
  ```
22
17
 
23
18
  ## Quick Start
24
19
 
25
20
  ```tsx
26
- import React from "react";
27
- import { StackRouterProvider } from "@webviewkit";
21
+ import { StackRouterProvider, useStackRouter } from "@ahnseungchan/webviewkit";
22
+
23
+ function Home() {
24
+ const { push } = useStackRouter();
25
+ return <button onClick={() => push("/about")}>About</button>;
26
+ }
28
27
 
29
- function App() {
28
+ function About() {
29
+ const { back } = useStackRouter();
30
+ return <button onClick={back}>Back</button>;
31
+ }
32
+
33
+ export default function App() {
30
34
  return (
31
35
  <StackRouterProvider
32
36
  maxWidth="600px"
37
+ enableDevTools
33
38
  Activities={[
34
- {
35
- path: "/",
36
- element: <HomePage />,
37
- },
38
- {
39
- path: "/about",
40
- element: <AboutPage />,
41
- },
39
+ { path: "/", element: <Home /> },
40
+ { path: "/about", element: <About /> },
42
41
  ]}
43
- initialActivity={{ path: "/", element: <HomePage /> }}
42
+ initialActivity={{ path: "/", element: <Home /> }}
44
43
  />
45
44
  );
46
45
  }
47
46
  ```
48
47
 
49
- ## Usage
48
+ ## Dev Stack Roadmap (1.2.0)
50
49
 
51
- ### Basic Navigation
50
+ 개발 모드에서 스택이 몇 장 쌓였는지 **눈으로** 확인할 수 있습니다.
52
51
 
53
- ```tsx
54
- import { useStackRouter } from "@webviewkit/core";
52
+ | 옵션 | 동작 |
53
+ |------|------|
54
+ | `enableDevTools={true}` | 우하단 로드맵 항상 표시 |
55
+ | `enableDevTools={false}` | 숨김 |
56
+ | 생략 | `development`에서만 표시, production에서는 숨김 |
55
57
 
56
- function HomePage() {
57
- const { push, back } = useStackRouter();
58
+ 로드맵에 표시되는 정보:
58
59
 
59
- return (
60
- <div>
61
- <h1>Home</h1>
62
- <button onClick={() => push("/about")}>Go to About</button>
63
- <button onClick={back}>Back</button>
64
- </div>
65
- );
66
- }
67
- ```
68
-
69
- ### History Management
60
+ - **stack × N** — 현재 스택 깊이
61
+ - **점→선** — 쌓인 페이지 순서
62
+ - **경로 목록** — 각 `path`, 최상단에 `← top`
70
63
 
71
64
  ```tsx
72
- import { history } from "@webviewkit";
73
-
74
- // Navigate to a new page
75
- await history.push("/new-page", { data: "some data" });
65
+ import { StackDevRoadmap } from "@ahnseungchan/webviewkit";
76
66
 
77
- // Replace current page
78
- await history.replace("/replacement-page", { data: "replacement data" });
79
-
80
- // Go back
81
- await history.back();
82
-
83
- // Go forward
84
- await history.forward();
67
+ // StackRouterProvider 없이 디버그 UI만 쓸 때
68
+ <StackDevRoadmap
69
+ pages={[{ path: "/" }, { path: "/detail" }]}
70
+ currentPath="/detail"
71
+ />
85
72
  ```
86
73
 
87
- ## API Reference
88
-
89
- ### StackRouterProvider Props
90
-
91
- | Prop | Type | Default | Description |
92
- | ----------------- | ------------------------------------------------- | --------- | ------------------------------------- |
93
- | `maxWidth` | `string` | `"600px"` | Maximum width of the layout container |
94
- | `isShowHeader` | `boolean` | `true` | Whether to show the header |
95
- | `Activities` | `Array<{path: string, element: React.ReactNode}>` | `[]` | Array of available routes |
96
- | `initialActivity` | `{path: string, element: React.ReactNode}` | - | Initial route to display |
97
-
98
- ### useStackRouter Hook
99
-
100
- Returns an object with navigation methods:
101
-
102
- - `currentPath`: Current active path
103
- - `currentState`: Current history state
104
- - `push(path, data?)`: Navigate to a new page
105
- - `replace(path, data?)`: Replace current page
106
- - `back()`: Go back to previous page
107
- - `forward()`: Go forward to next page
74
+ ## API
108
75
 
109
- ### History API
76
+ | Export | 설명 |
77
+ |--------|------|
78
+ | `StackRouterProvider` | 스택 라우터 루트 |
79
+ | `useStackRouter` | `push`, `back`, `replace`, `forward`, `visiblePages` |
80
+ | `history` | `pushState` / `popstate` 래퍼 (SSR-safe) |
81
+ | `BackRouter` | 뒤로가기 버튼 |
82
+ | `StackHeader` | 경로 표시 헤더 |
83
+ | `StackDevRoadmap` | 스택 로드맵 오버레이 |
84
+ | `WebViewBridgeProvider` | RN/Flutter 브릿지 (protocol v1) |
85
+ | `getStackRouterBridge` | web → native 스택 호출 |
86
+ | `STACK_TRANSITION_MS` | 전환 ms 상수 (`500`) |
87
+ | `Direction`, `HistoryAction` | 타입·enum |
110
88
 
111
- - `history.push(path, data?)`: Add new history entry
112
- - `history.replace(path, data?)`: Replace current history entry
113
- - `history.back()`: Go back to previous entry
114
- - `history.forward()`: Go forward to next entry
115
- - `history.go(delta)`: Go to specific history entry
116
- - `history.getCurrentState()`: Get current history state
117
- - `history.addListener(listener)`: Add history change listener
89
+ ## Native Bridge (1.3.0)
118
90
 
119
- ## Examples
91
+ 하이브리드 앱에서 **웹 스택 UI**와 **네이티브(WebView) 스택**을 같은 프로토콜로 맞출 때 사용합니다.
92
+ React Native · Flutter · 브라우저 단독 모두 지원합니다.
120
93
 
121
- See the `packages/browser` directory for complete examples.
122
-
123
- ## Development
124
-
125
- ```bash
126
- # Install dependencies
127
- pnpm install
128
-
129
- # Build all packages
130
- pnpm build
94
+ ```tsx
95
+ import { WebViewBridgeProvider, getStackRouterBridge } from "@ahnseungchan/webviewkit";
131
96
 
132
- # Run tests
133
- pnpm test
97
+ // native → web: router handlers / web → native: bridge client
98
+ <WebViewBridgeProvider stack={{ push, replace, pop, reset }} />;
134
99
 
135
- # Run linting
136
- pnpm lint
100
+ await getStackRouterBridge()?.push("/detail");
137
101
  ```
138
102
 
139
- ## Contributing
140
-
141
- 1. Fork the repository
142
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
143
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
144
- 4. Push to the branch (`git push origin feature/amazing-feature`)
145
- 5. Open a Pull Request
103
+ - Flutter: `stackPush` / `stackPop` 등 레거시 핸들러 또는 JSON envelope
104
+ - React Native: `ReactNativeWebView.postMessage`
105
+ - 프로토콜 상세: [docs/native-bridge-protocol.md](../../docs/native-bridge-protocol.md)
146
106
 
147
107
  ## License
148
108
 
149
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
109
+ MIT
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ import { type InstallBridgeHostOptions, type StackRouterHostHandlers } from "./host";
3
+ import { type CreateStackRouterBridgeOptions } from "./stack-router-bridge";
4
+ export type WebViewBridgeProviderProps = {
5
+ children?: ReactNode;
6
+ /** Native → web stack handlers (e.g. Next.js `router.push`) */
7
+ stack?: StackRouterHostHandlers;
8
+ transport?: CreateStackRouterBridgeOptions;
9
+ /** Install `window.stackRouterBridge` for web → native */
10
+ enableOutboundBridge?: boolean;
11
+ hostOptions?: Omit<InstallBridgeHostOptions, "stack" | "exposeStackRouterBridge" | "legacyInboundHandlers">;
12
+ };
13
+ /**
14
+ * Mount once near the app root (Next.js `layout`, SPA `App`).
15
+ * Wires protocol ingress + optional legacy window stack handlers.
16
+ */
17
+ export declare function WebViewBridgeProvider({ children, stack, transport, enableOutboundBridge, hostOptions, }: WebViewBridgeProviderProps): import("@emotion/react/jsx-runtime").JSX.Element;
18
+ //# sourceMappingURL=WebViewBridgeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebViewBridgeProvider.d.ts","sourceRoot":"","sources":["../../src/bridge/WebViewBridgeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC7B,MAAM,QAAQ,CAAC;AAChB,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,SAAS,CAAC,EAAE,8BAA8B,CAAC;IAC3C,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,WAAW,CAAC,EAAE,IAAI,CAChB,wBAAwB,EACxB,OAAO,GAAG,yBAAyB,GAAG,uBAAuB,CAC9D,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,QAAe,EACf,KAAK,EACL,SAAS,EACT,oBAA2B,EAC3B,WAAW,GACZ,EAAE,0BAA0B,oDAiB5B"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebViewBridgeProvider = WebViewBridgeProvider;
4
+ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const host_1 = require("./host");
7
+ const stack_router_bridge_1 = require("./stack-router-bridge");
8
+ /**
9
+ * Mount once near the app root (Next.js `layout`, SPA `App`).
10
+ * Wires protocol ingress + optional legacy window stack handlers.
11
+ */
12
+ function WebViewBridgeProvider({ children = null, stack, transport, enableOutboundBridge = true, hostOptions, }) {
13
+ (0, react_1.useEffect)(() => {
14
+ const outbound = enableOutboundBridge
15
+ ? (0, stack_router_bridge_1.createStackRouterBridge)(transport)
16
+ : undefined;
17
+ const cleanup = (0, host_1.installBridgeHost)({
18
+ stack,
19
+ legacyInboundHandlers: Boolean(stack),
20
+ exposeStackRouterBridge: outbound !== null && outbound !== void 0 ? outbound : false,
21
+ ...hostOptions,
22
+ });
23
+ return cleanup;
24
+ }, [stack, enableOutboundBridge, transport, hostOptions]);
25
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
26
+ }
@@ -0,0 +1,20 @@
1
+ import { type BridgeTransportOptions } from "./transport";
2
+ import { detectBridgeHost } from "./detect-host";
3
+ import type { BridgeTransport } from "./transport";
4
+ import { type BridgeEnvelope, type StackMeta } from "./protocol";
5
+ export type WebViewBridgeClientOptions = BridgeTransportOptions & {
6
+ transport?: BridgeTransport;
7
+ };
8
+ export declare class WebViewBridgeClient {
9
+ readonly host: ReturnType<typeof detectBridgeHost>;
10
+ private readonly transport;
11
+ constructor(options?: WebViewBridgeClientOptions);
12
+ send(envelope: BridgeEnvelope): Promise<void>;
13
+ request<TPayload = unknown>(method: string, payload?: TPayload): Promise<BridgeEnvelope<TPayload>>;
14
+ stackPush(url: string, meta?: StackMeta): Promise<BridgeEnvelope>;
15
+ stackReplace(url: string, meta?: StackMeta): Promise<BridgeEnvelope>;
16
+ stackPop(): Promise<BridgeEnvelope>;
17
+ stackReset(url: string, meta?: StackMeta): Promise<BridgeEnvelope>;
18
+ }
19
+ export declare function createWebViewBridgeClient(options?: WebViewBridgeClientOptions): WebViewBridgeClient;
20
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/bridge/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,SAAS,EAGf,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,GAAG;IAChE,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;gBAEhC,OAAO,GAAE,0BAA+B;IAM9C,IAAI,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,OAAO,CAAC,QAAQ,GAAG,OAAO,EAC9B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAMpC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC;IAOvE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC;IAO1E,QAAQ,IAAI,OAAO,CAAC,cAAc,CAAC;IAInC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC;CAMzE;AAED,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,0BAA0B,GACnC,mBAAmB,CAErB"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebViewBridgeClient = void 0;
4
+ exports.createWebViewBridgeClient = createWebViewBridgeClient;
5
+ const transport_1 = require("./transport");
6
+ const detect_host_1 = require("./detect-host");
7
+ const protocol_1 = require("./protocol");
8
+ class WebViewBridgeClient {
9
+ constructor(options = {}) {
10
+ var _a, _b, _c;
11
+ this.host = (_b = (_a = options.transport) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : (0, detect_host_1.detectBridgeHost)();
12
+ this.transport =
13
+ (_c = options.transport) !== null && _c !== void 0 ? _c : (0, transport_1.createBridgeTransport)(this.host, options);
14
+ }
15
+ async send(envelope) {
16
+ await this.transport.send(envelope);
17
+ }
18
+ async request(method, payload) {
19
+ const envelope = (0, protocol_1.createBridgeEnvelope)(method, payload);
20
+ await this.send(envelope);
21
+ return envelope;
22
+ }
23
+ stackPush(url, meta = null) {
24
+ return this.request(protocol_1.STACK_BRIDGE_METHODS.PUSH, {
25
+ url,
26
+ meta,
27
+ });
28
+ }
29
+ stackReplace(url, meta = null) {
30
+ return this.request(protocol_1.STACK_BRIDGE_METHODS.REPLACE, {
31
+ url,
32
+ meta,
33
+ });
34
+ }
35
+ stackPop() {
36
+ return this.request(protocol_1.STACK_BRIDGE_METHODS.POP);
37
+ }
38
+ stackReset(url, meta = null) {
39
+ return this.request(protocol_1.STACK_BRIDGE_METHODS.RESET, {
40
+ url,
41
+ meta,
42
+ });
43
+ }
44
+ }
45
+ exports.WebViewBridgeClient = WebViewBridgeClient;
46
+ function createWebViewBridgeClient(options) {
47
+ return new WebViewBridgeClient(options);
48
+ }
@@ -0,0 +1,4 @@
1
+ import type { BridgeHostKind } from "./protocol";
2
+ export declare function detectBridgeHost(): BridgeHostKind;
3
+ export declare function isNativeBridgeHost(host: BridgeHostKind): boolean;
4
+ //# sourceMappingURL=detect-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-host.d.ts","sourceRoot":"","sources":["../../src/bridge/detect-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAgB,gBAAgB,IAAI,cAAc,CAYjD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAEhE"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectBridgeHost = detectBridgeHost;
4
+ exports.isNativeBridgeHost = isNativeBridgeHost;
5
+ function detectBridgeHost() {
6
+ var _a, _b;
7
+ if (typeof window === "undefined")
8
+ return "unknown";
9
+ if ((_a = window.ReactNativeWebView) === null || _a === void 0 ? void 0 : _a.postMessage) {
10
+ return "react-native";
11
+ }
12
+ if ((_b = window.flutter_inappwebview) === null || _b === void 0 ? void 0 : _b.callHandler) {
13
+ return "flutter";
14
+ }
15
+ return "browser";
16
+ }
17
+ function isNativeBridgeHost(host) {
18
+ return host === "react-native" || host === "flutter";
19
+ }
@@ -0,0 +1,31 @@
1
+ import { type BridgeEnvelope, type StackMeta } from "./protocol";
2
+ import type { StackRouterBridge } from "./stack-router-bridge";
3
+ export type StackRouterHostHandlers = {
4
+ push: (url: string, meta?: StackMeta) => boolean | Promise<boolean>;
5
+ replace: (url: string, meta?: StackMeta) => boolean | Promise<boolean>;
6
+ pop: () => boolean | Promise<boolean>;
7
+ reset: (url: string, meta?: StackMeta) => boolean | Promise<boolean>;
8
+ };
9
+ export type InstallBridgeHostOptions = {
10
+ /** Native → web stack navigation */
11
+ stack?: StackRouterHostHandlers;
12
+ /** Custom method handlers keyed by envelope `method` */
13
+ handlers?: Record<string, (payload: unknown, envelope: BridgeEnvelope) => boolean | Promise<boolean>>;
14
+ /** Called for every valid envelope after routing */
15
+ onMessage?: (envelope: BridgeEnvelope) => void;
16
+ /**
17
+ * Expose `window.stackRouterBridge` for imperative web→native calls.
18
+ * Requires `createStackRouterBridge()` to be set separately or via `stackRouterBridge` option.
19
+ */
20
+ exposeStackRouterBridge?: StackRouterBridge | boolean;
21
+ /**
22
+ * Register `window.stackPush` / `stackPop` for Flutter native→web.
23
+ */
24
+ legacyInboundHandlers?: boolean;
25
+ };
26
+ export declare function handleBridgeMessage(raw: string | unknown, options: InstallBridgeHostOptions): Promise<boolean>;
27
+ export type BridgeHostCleanup = () => void;
28
+ export declare function installBridgeHost(options: InstallBridgeHostOptions): BridgeHostCleanup;
29
+ /** Emit a protocol envelope from web (e.g. for tests or custom native listeners) */
30
+ export declare function emitBridgeEnvelope(method: string, payload?: unknown): BridgeEnvelope;
31
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/bridge/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,GAAG,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,oCAAoC;IACpC,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CACf,MAAM,EACN,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAC3E,CAAC;IACF,oDAAoD;IACpD,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC;IACtD;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAqCF,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GAAG,OAAO,EACrB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC;AAE3C,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,iBAAiB,CAsCnB;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,OAAO,GAChB,cAAc,CAEhB"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleBridgeMessage = handleBridgeMessage;
4
+ exports.installBridgeHost = installBridgeHost;
5
+ exports.emitBridgeEnvelope = emitBridgeEnvelope;
6
+ const protocol_1 = require("./protocol");
7
+ async function dispatchEnvelope(envelope, options) {
8
+ var _a, _b, _c, _d, _e;
9
+ (_a = options.onMessage) === null || _a === void 0 ? void 0 : _a.call(options, envelope);
10
+ const custom = (_b = options.handlers) === null || _b === void 0 ? void 0 : _b[envelope.method];
11
+ if (custom) {
12
+ return Boolean(await custom(envelope.payload, envelope));
13
+ }
14
+ const stack = options.stack;
15
+ if (!stack)
16
+ return false;
17
+ const payload = envelope.payload;
18
+ switch (envelope.method) {
19
+ case protocol_1.STACK_BRIDGE_METHODS.PUSH:
20
+ if (!(payload === null || payload === void 0 ? void 0 : payload.url))
21
+ return false;
22
+ return Boolean(await stack.push(payload.url, (_c = payload.meta) !== null && _c !== void 0 ? _c : null));
23
+ case protocol_1.STACK_BRIDGE_METHODS.REPLACE:
24
+ if (!(payload === null || payload === void 0 ? void 0 : payload.url))
25
+ return false;
26
+ return Boolean(await stack.replace(payload.url, (_d = payload.meta) !== null && _d !== void 0 ? _d : null));
27
+ case protocol_1.STACK_BRIDGE_METHODS.POP:
28
+ return Boolean(await stack.pop());
29
+ case protocol_1.STACK_BRIDGE_METHODS.RESET:
30
+ if (!(payload === null || payload === void 0 ? void 0 : payload.url))
31
+ return false;
32
+ return Boolean(await stack.reset(payload.url, (_e = payload.meta) !== null && _e !== void 0 ? _e : null));
33
+ default:
34
+ return false;
35
+ }
36
+ }
37
+ function handleBridgeMessage(raw, options) {
38
+ const envelope = (0, protocol_1.parseBridgeMessage)(raw);
39
+ if (!envelope)
40
+ return Promise.resolve(false);
41
+ return dispatchEnvelope(envelope, options);
42
+ }
43
+ function installBridgeHost(options) {
44
+ if (typeof window === "undefined") {
45
+ return () => undefined;
46
+ }
47
+ const inbound = (raw) => {
48
+ void handleBridgeMessage(raw, options);
49
+ };
50
+ window.webviewkitBridge = { postMessage: inbound };
51
+ if (options.legacyInboundHandlers !== false && options.stack) {
52
+ const { stack } = options;
53
+ window.stackPush = (url, meta) => Promise.resolve(stack.push(url, meta !== null && meta !== void 0 ? meta : null)).then(Boolean);
54
+ window.stackReplace = (url, meta) => Promise.resolve(stack.replace(url, meta !== null && meta !== void 0 ? meta : null)).then(Boolean);
55
+ window.stackPop = () => Promise.resolve(stack.pop()).then(Boolean);
56
+ window.stackReset = (url, meta) => Promise.resolve(stack.reset(url, meta !== null && meta !== void 0 ? meta : null)).then(Boolean);
57
+ }
58
+ if (options.exposeStackRouterBridge) {
59
+ if (typeof options.exposeStackRouterBridge === "object") {
60
+ window.stackRouterBridge = options.exposeStackRouterBridge;
61
+ }
62
+ }
63
+ return () => {
64
+ delete window.webviewkitBridge;
65
+ delete window.stackPush;
66
+ delete window.stackReplace;
67
+ delete window.stackPop;
68
+ delete window.stackReset;
69
+ if (options.exposeStackRouterBridge) {
70
+ delete window.stackRouterBridge;
71
+ }
72
+ };
73
+ }
74
+ /** Emit a protocol envelope from web (e.g. for tests or custom native listeners) */
75
+ function emitBridgeEnvelope(method, payload) {
76
+ return (0, protocol_1.createBridgeEnvelope)(method, payload);
77
+ }
@@ -0,0 +1,10 @@
1
+ export { BRIDGE_PROTOCOL_VERSION, BRIDGE_NAMESPACE, STACK_BRIDGE_METHODS, FLUTTER_LEGACY_STACK_HANDLERS, FLUTTER_UNIFIED_HANDLER, RN_BRIDGE_HANDLER, createBridgeId, createBridgeEnvelope, isBridgeEnvelope, parseBridgeMessage, serializeBridgeMessage, } from "./protocol";
2
+ export type { BridgeHostKind, StackBridgeMethod, StackMeta, StackPushPayload, StackReplacePayload, StackResetPayload, BridgeEnvelope, BridgeErrorBody, } from "./protocol";
3
+ export { detectBridgeHost, isNativeBridgeHost } from "./detect-host";
4
+ export { createBridgeTransport, type BridgeTransport, type BridgeTransportOptions, } from "./transport";
5
+ export { WebViewBridgeClient, createWebViewBridgeClient, type WebViewBridgeClientOptions, } from "./client";
6
+ export { installBridgeHost, handleBridgeMessage, emitBridgeEnvelope, type InstallBridgeHostOptions, type StackRouterHostHandlers, type BridgeHostCleanup, } from "./host";
7
+ export { createStackRouterBridge, getStackRouterBridge, installStackRouterBridge, type StackRouterBridge, type CreateStackRouterBridgeOptions, } from "./stack-router-bridge";
8
+ export { WebViewBridgeProvider } from "./WebViewBridgeProvider";
9
+ export type { WebViewBridgeProviderProps } from "./WebViewBridgeProvider";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EACpB,6BAA6B,EAC7B,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,KAAK,0BAA0B,GAChC,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,GACvB,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EACxB,KAAK,iBAAiB,EACtB,KAAK,8BAA8B,GACpC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebViewBridgeProvider = exports.installStackRouterBridge = exports.getStackRouterBridge = exports.createStackRouterBridge = exports.emitBridgeEnvelope = exports.handleBridgeMessage = exports.installBridgeHost = exports.createWebViewBridgeClient = exports.WebViewBridgeClient = exports.createBridgeTransport = exports.isNativeBridgeHost = exports.detectBridgeHost = exports.serializeBridgeMessage = exports.parseBridgeMessage = exports.isBridgeEnvelope = exports.createBridgeEnvelope = exports.createBridgeId = exports.RN_BRIDGE_HANDLER = exports.FLUTTER_UNIFIED_HANDLER = exports.FLUTTER_LEGACY_STACK_HANDLERS = exports.STACK_BRIDGE_METHODS = exports.BRIDGE_NAMESPACE = exports.BRIDGE_PROTOCOL_VERSION = void 0;
4
+ var protocol_1 = require("./protocol");
5
+ Object.defineProperty(exports, "BRIDGE_PROTOCOL_VERSION", { enumerable: true, get: function () { return protocol_1.BRIDGE_PROTOCOL_VERSION; } });
6
+ Object.defineProperty(exports, "BRIDGE_NAMESPACE", { enumerable: true, get: function () { return protocol_1.BRIDGE_NAMESPACE; } });
7
+ Object.defineProperty(exports, "STACK_BRIDGE_METHODS", { enumerable: true, get: function () { return protocol_1.STACK_BRIDGE_METHODS; } });
8
+ Object.defineProperty(exports, "FLUTTER_LEGACY_STACK_HANDLERS", { enumerable: true, get: function () { return protocol_1.FLUTTER_LEGACY_STACK_HANDLERS; } });
9
+ Object.defineProperty(exports, "FLUTTER_UNIFIED_HANDLER", { enumerable: true, get: function () { return protocol_1.FLUTTER_UNIFIED_HANDLER; } });
10
+ Object.defineProperty(exports, "RN_BRIDGE_HANDLER", { enumerable: true, get: function () { return protocol_1.RN_BRIDGE_HANDLER; } });
11
+ Object.defineProperty(exports, "createBridgeId", { enumerable: true, get: function () { return protocol_1.createBridgeId; } });
12
+ Object.defineProperty(exports, "createBridgeEnvelope", { enumerable: true, get: function () { return protocol_1.createBridgeEnvelope; } });
13
+ Object.defineProperty(exports, "isBridgeEnvelope", { enumerable: true, get: function () { return protocol_1.isBridgeEnvelope; } });
14
+ Object.defineProperty(exports, "parseBridgeMessage", { enumerable: true, get: function () { return protocol_1.parseBridgeMessage; } });
15
+ Object.defineProperty(exports, "serializeBridgeMessage", { enumerable: true, get: function () { return protocol_1.serializeBridgeMessage; } });
16
+ var detect_host_1 = require("./detect-host");
17
+ Object.defineProperty(exports, "detectBridgeHost", { enumerable: true, get: function () { return detect_host_1.detectBridgeHost; } });
18
+ Object.defineProperty(exports, "isNativeBridgeHost", { enumerable: true, get: function () { return detect_host_1.isNativeBridgeHost; } });
19
+ var transport_1 = require("./transport");
20
+ Object.defineProperty(exports, "createBridgeTransport", { enumerable: true, get: function () { return transport_1.createBridgeTransport; } });
21
+ var client_1 = require("./client");
22
+ Object.defineProperty(exports, "WebViewBridgeClient", { enumerable: true, get: function () { return client_1.WebViewBridgeClient; } });
23
+ Object.defineProperty(exports, "createWebViewBridgeClient", { enumerable: true, get: function () { return client_1.createWebViewBridgeClient; } });
24
+ var host_1 = require("./host");
25
+ Object.defineProperty(exports, "installBridgeHost", { enumerable: true, get: function () { return host_1.installBridgeHost; } });
26
+ Object.defineProperty(exports, "handleBridgeMessage", { enumerable: true, get: function () { return host_1.handleBridgeMessage; } });
27
+ Object.defineProperty(exports, "emitBridgeEnvelope", { enumerable: true, get: function () { return host_1.emitBridgeEnvelope; } });
28
+ var stack_router_bridge_1 = require("./stack-router-bridge");
29
+ Object.defineProperty(exports, "createStackRouterBridge", { enumerable: true, get: function () { return stack_router_bridge_1.createStackRouterBridge; } });
30
+ Object.defineProperty(exports, "getStackRouterBridge", { enumerable: true, get: function () { return stack_router_bridge_1.getStackRouterBridge; } });
31
+ Object.defineProperty(exports, "installStackRouterBridge", { enumerable: true, get: function () { return stack_router_bridge_1.installStackRouterBridge; } });
32
+ var WebViewBridgeProvider_1 = require("./WebViewBridgeProvider");
33
+ Object.defineProperty(exports, "WebViewBridgeProvider", { enumerable: true, get: function () { return WebViewBridgeProvider_1.WebViewBridgeProvider; } });
@@ -0,0 +1,51 @@
1
+ /** WebViewKit native bridge protocol (v1) */
2
+ export declare const BRIDGE_PROTOCOL_VERSION: 1;
3
+ export declare const BRIDGE_NAMESPACE: "webviewkit";
4
+ export type BridgeHostKind = "react-native" | "flutter" | "browser" | "unknown";
5
+ /** Stack navigation — maps to common `stackPush` / `stackPop` WebView handlers */
6
+ export declare const STACK_BRIDGE_METHODS: {
7
+ readonly PUSH: "stack.push";
8
+ readonly REPLACE: "stack.replace";
9
+ readonly POP: "stack.pop";
10
+ readonly RESET: "stack.reset";
11
+ };
12
+ export type StackBridgeMethod = (typeof STACK_BRIDGE_METHODS)[keyof typeof STACK_BRIDGE_METHODS];
13
+ export type StackMeta = Record<string, unknown> | null;
14
+ export type StackPushPayload = {
15
+ url: string;
16
+ meta?: StackMeta;
17
+ };
18
+ export type StackReplacePayload = StackPushPayload;
19
+ export type StackResetPayload = {
20
+ url: string;
21
+ meta?: StackMeta;
22
+ };
23
+ export type BridgeErrorBody = {
24
+ code: string;
25
+ message: string;
26
+ };
27
+ export type BridgeEnvelope<TPayload = unknown> = {
28
+ v: typeof BRIDGE_PROTOCOL_VERSION;
29
+ ns: typeof BRIDGE_NAMESPACE;
30
+ id: string;
31
+ method: string;
32
+ payload?: TPayload;
33
+ error?: BridgeErrorBody;
34
+ };
35
+ export declare function createBridgeId(): string;
36
+ export declare function createBridgeEnvelope<TPayload>(method: string, payload?: TPayload, id?: string): BridgeEnvelope<TPayload>;
37
+ export declare function isBridgeEnvelope(value: unknown): value is BridgeEnvelope;
38
+ export declare function parseBridgeMessage(raw: string | unknown): BridgeEnvelope | null;
39
+ export declare function serializeBridgeMessage(msg: BridgeEnvelope): string;
40
+ /** Flutter InAppWebView legacy handler names */
41
+ export declare const FLUTTER_LEGACY_STACK_HANDLERS: {
42
+ readonly push: "stackPush";
43
+ readonly replace: "stackReplace";
44
+ readonly pop: "stackPop";
45
+ readonly reset: "stackReset";
46
+ };
47
+ /** Unified handler name when native listens on one channel */
48
+ export declare const FLUTTER_UNIFIED_HANDLER = "webviewkitBridge";
49
+ /** React Native receives messages via this global handler name suggestion */
50
+ export declare const RN_BRIDGE_HANDLER = "webviewkitBridge";
51
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/bridge/protocol.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,eAAO,MAAM,uBAAuB,EAAG,CAAU,CAAC;AAClD,eAAO,MAAM,gBAAgB,EAAG,YAAqB,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAC3B,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAEnE,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;AAEvD,MAAM,MAAM,gBAAgB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AACjE,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AACnD,MAAM,MAAM,iBAAiB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,MAAM,MAAM,cAAc,CAAC,QAAQ,GAAG,OAAO,IAAI;IAC/C,CAAC,EAAE,OAAO,uBAAuB,CAAC;IAClC,EAAE,EAAE,OAAO,gBAAgB,CAAC;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAC3C,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,QAAQ,EAClB,EAAE,CAAC,EAAE,MAAM,GACV,cAAc,CAAC,QAAQ,CAAC,CAQ1B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CASxE;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,GAAG,OAAO,GACpB,cAAc,GAAG,IAAI,CAQvB;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAElE;AAED,gDAAgD;AAChD,eAAO,MAAM,6BAA6B;;;;;CAKhC,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,qBAAqB,CAAC"}