@fawadmahmoodwashmen/washmen-js-native-bridge 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Washmen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ # washmen-js-native-bridge
2
+
3
+ Reusable **bidirectional JSON-RPC** bridge between a **React** site (running inside `react-native-webview`) and **React Native**.
4
+
5
+ - **Protocol:** `wm: 2` with `rpc: 1` (web → native) and `rpc: 3` (web reply to native `invokeWebMethod`).
6
+ - **Canonical implementation surface:** [`src/wm-webview-bridge.ts`](./src/wm-webview-bridge.ts) (bootstrap string, parsers, RN hook).
7
+
8
+ Published to the **public npm registry** — default install, no `.npmrc` required.
9
+
10
+ ```bash
11
+ npm install @fawadmahmoodwashmen/washmen-js-native-bridge
12
+ ```
13
+
14
+ **Peer:** `react` ≥ 18. Optional: `react-native`, `react-native-webview` (RN app).
15
+
16
+ **Entry points**
17
+
18
+ | Import path | Use in |
19
+ |-------------|--------|
20
+ | `@fawadmahmoodwashmen/washmen-js-native-bridge` | React **web** (in WebView) + re-exports protocol helpers |
21
+ | `@fawadmahmoodwashmen/washmen-js-native-bridge/native` or `…/wm-webview-bridge` | React **Native** (bootstrap, parsers, `useWmWebViewBridge`) |
22
+
23
+ ---
24
+
25
+ ## Web → native (`rpc: 1`)
26
+
27
+ 1. RN injects `buildWmRpcBridgeBootstrapScript({ globalName: "globalRef" })` **before** content loads.
28
+ 2. That script defines:
29
+ - `window.__WM_RPC__._deliver(id, ok, jsonStringPayload)`
30
+ - `window.__WM_BIDIR__.register / unregister / fromNative`
31
+ - `window.globalRef` (default) — a **`Proxy`** so **any** `globalRef.someMethod(...args)` returns a `Promise` and posts:
32
+
33
+ ```json
34
+ { "wm": 2, "rpc": 1, "id": "<id>", "m": "<methodName>", "a": [/* args, JSON-serializable */] }
35
+ ```
36
+
37
+ 3. RN `onMessage` (via `useWmWebViewBridge`) runs your handler, then **`injectJavaScript`**:
38
+
39
+ ```js
40
+ window.__WM_RPC__._deliver("id", true, "{\"quotes\":[]}"); true;
41
+ ```
42
+
43
+ (`buildWmRpcDeliverScript` does this safely.)
44
+
45
+ **Web (typed):**
46
+
47
+ ```tsx
48
+ import { createNativeRpcClient, WmNativeApi } from "@fawadmahmoodwashmen/washmen-js-native-bridge";
49
+
50
+ // Augment WmNativeApi in your app (declaration merge) for IDE types.
51
+ const bridge = createNativeRpcClient<WmNativeApi>();
52
+ const quotes = await bridge.getQuotes({ author: "x" });
53
+ ```
54
+
55
+ If `ReactNativeWebView` is missing (Storybook / browser), `createNativeRpcClient` returns a **dead** proxy whose methods reject with a clear error.
56
+
57
+ ### Native shell back helper
58
+
59
+ ```tsx
60
+ import { BACK_BUTTON_ARIA_LABEL, useNativeShell } from "@fawadmahmoodwashmen/washmen-js-native-bridge";
61
+
62
+ function FooterBackButton() {
63
+ const { requestBack } = useNativeShell();
64
+ return (
65
+ <button
66
+ type="button"
67
+ aria-label={BACK_BUTTON_ARIA_LABEL}
68
+ onClick={() => void requestBack()}
69
+ >
70
+ Back
71
+ </button>
72
+ );
73
+ }
74
+ ```
75
+
76
+ In SPAs, always use a real `<button type="button">` and call `requestBack()` in `onClick`.
77
+ Do not rely on injected click-capture scripts for first-party React UI controls.
78
+
79
+ ---
80
+
81
+ ## Native → web (`invokeWebMethod` + `rpc: 3`)
82
+
83
+ 1. Same bootstrap defines `__WM_BIDIR__.fromNative(callId, method, argsJsonStr)`.
84
+ 2. RN calls `invokeWebMethod("getPageSummaryForNative", [])`, which **`injectJavaScript`**s `fromNative(...)`.
85
+ 3. The page runs the registered handler (see below), then **`postMessage`**:
86
+
87
+ ```json
88
+ { "wm": 2, "rpc": 3, "callId": "<id>", "ok": true, "payload": "<JSON string of result>" }
89
+ ```
90
+
91
+ On failure: `"ok": false`, `payload` is a JSON string like `{"message":"…"}`.
92
+
93
+ **Web — register handlers (React):**
94
+
95
+ ```tsx
96
+ import { WmWebBridgeProvider } from "@fawadmahmoodwashmen/washmen-js-native-bridge";
97
+
98
+ <WmWebBridgeProvider
99
+ handlers={{
100
+ getPageSummaryForNative: async () => [{ title: "Intro" }],
101
+ }}
102
+ >
103
+ <App />
104
+ </WmWebBridgeProvider>
105
+ ```
106
+
107
+ Or imperative: `registerWebRpcHandlers({ ... })` → returns `unregister()`.
108
+
109
+ ---
110
+
111
+ ## React Native — `useWmWebViewBridge`
112
+
113
+ ```tsx
114
+ import WebView from "react-native-webview";
115
+ import { useRef, useCallback } from "react";
116
+ import { useWmWebViewBridge } from "@fawadmahmoodwashmen/washmen-js-native-bridge/native";
117
+
118
+ export function BridgeWebView() {
119
+ const ref = useRef<WebView>(null);
120
+ const getHandlers = useCallback(
121
+ () => ({
122
+ getQuotes: async (_ctx, args: unknown[]) => {
123
+ const q = args[0] as { author: string };
124
+ return [{ id: "1", text: `Hello ${q.author}` }];
125
+ },
126
+ }),
127
+ [],
128
+ );
129
+
130
+ const { injectedJavaScriptBeforeContentLoaded, onMessage, invokeWebMethod } =
131
+ useWmWebViewBridge(getHandlers, ref, { globalName: "globalRef" });
132
+
133
+ async function askWeb() {
134
+ const summary = await invokeWebMethod("getPageSummaryForNative", []);
135
+ console.log(summary);
136
+ }
137
+
138
+ return (
139
+ <WebView
140
+ ref={ref}
141
+ source={{ uri: "https://your-web-app.com" }}
142
+ injectedJavaScriptBeforeContentLoaded={injectedJavaScriptBeforeContentLoaded}
143
+ onMessage={onMessage}
144
+ />
145
+ );
146
+ }
147
+ ```
148
+
149
+ **`onMessage` ordering:** `useWmWebViewBridge` / `handleWmWebViewBridgeMessage` parses **`rpc: 3` first**, then **`rpc: 1`**, so web replies never look like new web-initiated RPCs.
150
+
151
+ ---
152
+
153
+ ## Layout & scrolling (Android)
154
+
155
+ If the `WebView` sits **inside** a `ScrollView`, give the WebView a **fixed height** (or `flex:1` inside a bounded parent) and set **`nestedScrollEnabled`** on Android so nested scrolling behaves.
156
+
157
+ ---
158
+
159
+ ## Modular RN handlers (recommended)
160
+
161
+ Keep `getHandlers` as `useCallback` returning a **small map**; implement each method in its own module and compose:
162
+
163
+ ```ts
164
+ const getHandlers = useCallback(
165
+ () => ({
166
+ ...quotesHandlers(queryClient),
167
+ ...authHandlers(store),
168
+ }),
169
+ [queryClient, store],
170
+ );
171
+ ```
172
+
173
+ Do **not** paste large business logic into the bootstrap string — only `buildWmRpcBridgeBootstrapScript()` (transport + Proxy).
174
+
175
+ ---
176
+
177
+ ## Types: `WmNativeApi` / `WmWebApi`
178
+
179
+ Export interfaces in the package; **augment** in your apps so `createNativeRpcClient` / `invokeWebMethod` stay aligned:
180
+
181
+ ```ts
182
+ declare module "@fawadmahmoodwashmen/washmen-js-native-bridge" {
183
+ interface WmNativeApi {
184
+ getQuotes(input: { author: string }): Promise<{ id: string; text: string }[]>;
185
+ }
186
+ interface WmWebApi {
187
+ getPageSummaryForNative(): Promise<{ title: string }[]>;
188
+ }
189
+ }
190
+ ```
191
+
192
+ (Optional next step: codegen from a shared `bridge.contract.ts` in a monorepo.)
193
+
194
+ ---
195
+
196
+ ## Flow (bullets)
197
+
198
+ **Web → native**
199
+
200
+ - Web: `await globalRef.method(...args)` (Proxy) → `postMessage` `rpc:1`.
201
+ - Native: handler → `injectJavaScript` → `__WM_RPC__._deliver` → Promise resolves on web.
202
+
203
+ **Native → web**
204
+
205
+ - Native: `invokeWebMethod(method, args)` → `injectJavaScript` → `__WM_BIDIR__.fromNative`.
206
+ - Web: registered handler → `postMessage` `rpc:3`.
207
+ - Native: `onMessage` matches `callId`, resolves `invokeWebMethod` Promise.
208
+
209
+ ---
210
+
211
+ ## Non-goals
212
+
213
+ No binary streams, no non-JSON payloads, no synchronous return across the WebView boundary (async-only from RN’s perspective for cross-runtime calls).
214
+
215
+ ---
216
+
217
+ ## Releasing
218
+
219
+ Requires **Node.js ≥ 20.12** (for `release-it`). No **`GITHUB_TOKEN`** needed: GitHub Releases are disabled; publishing is **npm only**.
220
+
221
+ GitHub **environment** `Publishing` with secret **`NPM_TOKEN`** ([npm access token](https://docs.npmjs.com/creating-and-viewing-access-tokens) that can publish this scope). The workflow job uses `environment: Publishing` so that secret is available.
222
+
223
+ From a clean `main` working tree:
224
+
225
+ ```bash
226
+ npm run release
227
+ ```
228
+
229
+ You choose **patch / minor / major**, confirm steps. That runs tests + typecheck, bumps `package.json` + lockfile, commits, pushes, and creates tag **`vX.Y.Z`**. Pushing the tag triggers the **Publish npm** GitHub Action, which runs `npm publish` to **registry.npmjs.org**.
230
+
231
+ Dry run: `npm run release:dry`
232
+
233
+ ---
234
+
235
+ ## License
236
+
237
+ MIT