{"id":25352282,"url":"https://github.com/kwan3854/unity-webviewrpc","last_synced_at":"2025-04-09T00:04:05.996Z","repository":{"id":280210533,"uuid":"931993152","full_name":"kwan3854/Unity-WebViewRpc","owner":"kwan3854","description":"Unity WebView RPC provides an abstraction layer that allows communication between the Unity client (C#) and WebView (HTML, JS) using protobuf, similar to gRPC.","archived":false,"fork":false,"pushed_at":"2025-02-17T00:28:37.000Z","size":478,"stargazers_count":7,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-06T05:35:44.175Z","etag":null,"topics":["grpc","protobuf","rpc","unity","webview"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kwan3854.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["kwan3854"]}},"created_at":"2025-02-13T07:29:23.000Z","updated_at":"2025-03-17T05:23:05.000Z","dependencies_parsed_at":"2025-03-02T02:12:21.512Z","dependency_job_id":"230a124c-617d-4d67-97ad-2b7ed74fbc32","html_url":"https://github.com/kwan3854/Unity-WebViewRpc","commit_stats":null,"previous_names":["kwan3854/unity-webviewrpc"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-WebViewRpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-WebViewRpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-WebViewRpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kwan3854%2FUnity-WebViewRpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kwan3854","download_url":"https://codeload.github.com/kwan3854/Unity-WebViewRpc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247947858,"owners_count":21023066,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grpc","protobuf","rpc","unity","webview"],"created_at":"2025-02-14T18:38:36.065Z","updated_at":"2025-04-09T00:04:05.989Z","avatar_url":"https://github.com/kwan3854.png","language":"C#","funding_links":["https://github.com/sponsors/kwan3854"],"categories":[],"sub_categories":[],"readme":"[English](README.md) | [Korean](README_ko.md)\n\n[![openupm](https://img.shields.io/npm/v/com.kwanjoong.webviewrpc?label=openupm\u0026registry_uri=https://package.openupm.com)](https://openupm.com/packages/com.kwanjoong.webviewrpc/)\n[![License](https://img.shields.io/badge/License-MIT-brightgreen.svg)](LICENSE.md)\n\n# Unity WebView RPC\n\nUnity WebView RPC provides an abstraction layer that allows communication between the Unity client (C#) and WebView (HTML, JS) using protobuf, similar to gRPC.\nIt extends the traditional `JavaScript bridge` communication method to work similarly to a Remote Procedure Call (RPC).\nTo avoid dependency on a specific WebView library, it provides a Bridge interface so that communication can be achieved with the same code, regardless of the WebView library used.\n\n## Architecture\n\nWebView RPC simplifies the workflow compared to the traditional `JavaScript bridge` method.\n\n```mermaid\nflowchart LR\n    subgraph Traditional Method\n        direction LR\n        A[Unity C#] \u003c--\u003e B[Data Class\u003cbr\u003e\u0026 Manual Method Implementation]\n        B \u003c--\u003e C[JSON Parser]\n        C \u003c--\u003e D[JavaScript Bridge]\n        D \u003c--\u003e E[JSON Parser]\n        E \u003c--\u003e F[Data Class\u003cbr\u003e\u0026 Manual Method Implementation]\n        F \u003c--\u003e G[JavaScript WebView]\n    end\n\n    subgraph WebView RPC Method\n        direction LR\n        H[Unity C#\u003cbr\u003eDirect Method Call] \u003c--\u003e I[Magic Space]\n        I \u003c--\u003e J[JavaScript\u003cbr\u003eDirect Method Call]\n    end\n```\n\n### Internal Implementation\n\nInternally, WebView RPC is structured as follows:\n\n```mermaid\nflowchart LR\n    A[Unity C# Direct Call] \u003c--\u003e B[protobuf-generated C# Code] \u003c--\u003e C[protobuf Serializer/Deserializer] \u003c--\u003e D[Base64 Serializer/Deserializer] \u003c--\u003e E[JavaScript Bridge] \u003c--\u003e F[Base64 Serializer/Deserializer] \u003c--\u003e G[protobuf Serializer/Deserializer] \u003c--\u003e H[protobuf-generated JavaScript Code] \u003c--\u003e I[JavaScript Direct Call]\n```\n\n1. **Unity C# Direct Call**\n    - Calls an RPC interface function like a regular method in Unity.\n2. **protobuf-generated C# Code**\n    - Auto-generated C# wrapper/stub from the proto definition.\n    - RPC methods and data structures are based on protobuf.\n3. **Base64 Serializer + JavaScript Bridge**\n    - Converts raw byte data to Base64 before sending it through the WebView (browser).\n    - JavaScript receives the same format.\n4. **protobuf-generated JavaScript Code**\n    - Auto-generated JavaScript code from the same proto definition.\n    - Deserializes the serialized data from C# and directly calls JavaScript methods.\n\nWith WebView RPC, method calls between C# and JavaScript behave like regular function calls, significantly reducing the need for repetitive JSON parsing and bridge implementations. This structure becomes even more maintainable as the project scales.\n\n## Examples\n### Unity Example\nClone this whole repository.\n\u003e [!NOTE]\n\u003e Require [Viewplex Webview Asset(paid asset)](https://assetstore.unity.com/publishers/40309?locale=ko-KR\u0026srsltid=AfmBOoqtnjTpJ-pw_5iGoS88XRtGX-tY2eJmP86PYoYCOhxrvz1OXRaJ) to run sample.\n### Javascript Example\n[webview_rpc_sample](https://github.com/kwan3854/Unity-WebViewRpc/tree/main/webview_rpc_sample)\n```bash\n# 1. Move to sample directory\ncd webview_rpc_sample\n\n# 2. Install dependencies\nnpm install\n\n# 3. Build project\nnpm run build\n```\n\n## Installation\n\n### Adding WebView RPC to a Unity Project\n\n1. Install the `Protobuf` package via NuGet Package Manager.\n2. Install the WebViewRpc package either via Package Manager or OpenUPM.\n\n- Add to `Packages/manifest.json`:\n\n  ```json\n  {\n   \"dependencies\": {\n     \"com.kwanjoong.webviewrpc\": \"https://github.com/kwan3854/Unity-WebViewRpc.git?path=/Packages/WebviewRpc\"\n   }\n  }\n  ```\n\n- Or via Package Manager:\n\n    1. `Window` → `Package Manager` → `Add package from git URL...`\n    2. Enter: `https://github.com/kwan3854/Unity-WebViewRpc.git?path=/Packages/WebviewRpc`\n\n- Or via OpenUPM:\n\n  ```bash\n  openupm add com.kwanjoong.webviewrpc\n  ```\n\n### Adding `app-webview-rpc` Library to Javascript Side\n[npm package](https://www.npmjs.com/package/app-webview-rpc)\n#### Install\n```bash\nnpm install app-webview-rpc\n```\n\n#### Usage\n```javascript\nimport { VuplexBridge, WebViewRpcClient, WebViewRpcServer } from 'app-webview-rpc';\n\n// RPC client\nconst bridge = new VuplexBridge();\nconst rpcClient = new WebViewRpcClient(bridge);\n\n// RPC server\nconst rpcServer = new WebViewRpcServer(bridge);\n```\n\n### Installing the protobuf Compiler\n\n#### Convert protobuf files to C# and JavaScript using `protoc`.\n\n**Mac**\n\n```bash\nbrew install protobuf\nprotoc --version  # Ensure compiler version is 3+\n```\n\n**Windows**\n\n```bash\nwinget install protobuf\nprotoc --version  # Ensure compiler version is 3+\n```\n\n**Linux**\n\n```bash\napt install -y protobuf-compiler\nprotoc --version  # Ensure compiler version is 3+\n```\n\n### Installing the WebView RPC Code Generator\n\nDownload the latest release from the [WebViewRPC Code Generator repository](https://github.com/kwan3854/ProtocGenWebviewRpc).\n\n- **Windows**: `protoc-gen-webviewrpc.exe`\n- **Mac**: `protoc-gen-webviewrpc`\n- **Linux**: Not provided (requires manual build).\n\n## Quick Start\n\nHelloWorld is a simple RPC service that receives a `HelloRequest` message and returns a `HelloResponse` message. In this example, we will implement HelloWorld and verify communication between the Unity client and the WebView client.\n\nThe HelloWorld service takes a `HelloRequest` and returns a `HelloResponse`. First, let’s look at the example where the C# side acts as the server and the JavaScript side acts as the client.\n\n### Defining the protobuf File\n\n- protobuf is used to define the request and response formats of the service.\n- When the Unity client and the WebView have items to communicate, define the protobuf through discussion.\n- The following example is the `HelloWorld.proto` file, defining `HelloRequest`, `HelloResponse`, and the `HelloService` service.\n- In this example, the client side (JavaScript) calls the `SayHello` method, and the server side (C#) implements the `SayHello` method to process the request and return a response.\n\n```protobuf\nsyntax = \"proto3\";\n\npackage helloworld;\n\n// (Can be used as the namespace when generated in C#)\noption csharp_namespace = \"HelloWorld\";\n\n// Request message\nmessage HelloRequest {\n  string name = 1;\n}\n\n// Response message\nmessage HelloResponse {\n  string greeting = 1;\n}\n\n// Simple example service\nservice HelloService {\n  // [one-way] Request -\u003e Response\n  rpc SayHello (HelloRequest) returns (HelloResponse);\n}\n```\n\n### Generating C# and JavaScript from protobuf\n\n- We use the `protoc` compiler to convert the protobuf file into C# and JavaScript.\n- The `protoc` compiler transforms protobuf files into C# and JavaScript.\n- A [customized code generator](https://github.com/kwan3854/ProtocGenWebviewRpc) for WebView RPC is also available.\n- Run the following commands to generate C# and JavaScript code from the protobuf file.\n\n#### C# Common Code Generation (used by both server and client)\n\n```bash\nprotoc -I. --csharp_out=. HelloWorld.proto \n\n// This produces HelloWorld.cs.\n```\n\n#### C# Server Code Generation\n\n```bash\nprotoc \\\n  --plugin=protoc-gen-webviewrpc=./protoc-gen-webviewrpc \\\n  --webviewrpc_out=cs_server:. \\\n  -I. HelloWorld.proto\n  \n// This produces HelloWorld_HelloServiceBase.cs.\n```\n\n#### JavaScript Common Code Generation (for both client and server)\n\n\u003e [!IMPORTANT]\n\u003e [pbjs library is required.](https://www.npmjs.com/package/pbjs)\n\n```bash\nnpx pbjs HelloWorld.proto --es6 hello_world.js\n\n// This produces hello_world.js.\n// Recommend setting the output filename to the same name as the service defined in the protobuf file.\n```\n\n#### JavaScript Client Code Generation\n\n```bash\nprotoc \\\n  --plugin=protoc-gen-webviewrpc=./protoc-gen-webviewrpc \\\n  --webviewrpc_out=js_client:. \\\n  -I. HelloWorld.proto\n  \n// This produces HelloWorld_HelloServiceClient.js.\n```\n\n### Adding the Generated Code to Each Project\n\n- Add the generated code to each respective project.\n- You can use a GitHub action so that code is automatically generated and added to your project.\n\n### Implementing Bridge Code\n\n- The bridge code mediates communication between C# and JavaScript.\n- WebViewRpc is abstracted so it can be used with any WebView library.\n- Implement the bridge code according to your chosen WebView library.\n- Below is an example using Viewplex’s CanvasWebViewPrefab.\n\n```csharp\nusing System;\nusing Vuplex.WebView;\nusing WebViewRPC;\n\npublic class ViewplexWebViewBridge : IWebViewBridge\n{\n    public event Action\u003cstring\u003e OnMessageReceived;\n    private readonly CanvasWebViewPrefab _webViewPrefab;\n\n    public ViewplexWebViewBridge(CanvasWebViewPrefab webViewPrefab)\n    {\n        _webViewPrefab = webViewPrefab;\n\n        _webViewPrefab.WebView.MessageEmitted += (sender, args) =\u003e\n        {\n            OnMessageReceived?.Invoke(args.Value);\n        };\n    }\n\n    public void SendMessageToWeb(string message)\n    {\n        _webViewPrefab.WebView.PostMessage(message);\n    }\n}\n```\n\n```javascript\nexport class VuplexBridge {\n    constructor() {\n        this._onMessageCallback = null;\n        this._isVuplexReady = false;\n        this._pendingMessages = [];\n\n        // 1) If window.vuplex already exists, use it immediately\n        if (window.vuplex) {\n            this._isVuplexReady = true;\n        } else {\n            // Otherwise, wait for the 'vuplexready' event\n            window.addEventListener('vuplexready', () =\u003e {\n                this._isVuplexReady = true;\n                // Send all pending messages\n                for (const msg of this._pendingMessages) {\n                    window.vuplex.postMessage(msg);\n                }\n                this._pendingMessages = [];\n            });\n        }\n\n        // 2) C# -\u003e JS messages: \"vuplexmessage\" event\n        //    event.value contains the string (sent by C# PostMessage)\n        window.addEventListener('vuplexmessage', event =\u003e {\n            const base64Str = event.value; // Typically Base64\n            if (this._onMessageCallback) {\n                this._onMessageCallback(base64Str);\n            }\n        });\n    }\n\n    /**\n     * JS -\u003e C#: sends string (base64Str)\n     */\n    sendMessage(base64Str) {\n        // Vuplex serializes JS objects to JSON,\n        // but if we pass a string, it sends the string as is.\n        if (this._isVuplexReady \u0026\u0026 window.vuplex) {\n            window.vuplex.postMessage(base64Str);\n        } else {\n            // If vuplex isn’t ready yet, store messages in a queue\n            this._pendingMessages.push(base64Str);\n        }\n    }\n\n    /**\n     * onMessage(cb): registers a callback to receive strings from C#\n     */\n    onMessage(cb) {\n        this._onMessageCallback = cb;\n    }\n}\n```\n\n### Writing C# Server and JavaScript Client Code\n\n```csharp\npublic class WebViewRpcTester : MonoBehaviour\n{\n    [SerializeField] private CanvasWebViewPrefab webViewPrefab;\n\n    private async void Start()\n    {\n        await InitializeWebView(webViewPrefab);\n\n        // Create the bridge\n        var bridge = new ViewplexWebViewBridge(webViewPrefab);\n        // Create the server\n        var server = new WebViewRPC.WebViewRpcServer(bridge)\n        {\n            Services =\n            {\n                // Bind HelloService\n                HelloService.BindService(new HelloWorldService()),\n                // Add other services if necessary\n            }\n        };\n\n        // Start the server\n        server.Start();\n    }\n\n    private async Task InitializeWebView(CanvasWebViewPrefab webView)\n    {\n        // Example uses Viewplex’s CanvasWebViewPrefab\n        await webView.WaitUntilInitialized();\n        webView.WebView.LoadUrl(\"http://localhost:8081\");\n        await webView.WebView.WaitForNextPageLoadToFinish();\n    }\n}\n```\n\n```csharp\nusing HelloWorld;\nusing UnityEngine;\n\nnamespace SampleRpc\n{\n    // Inherit HelloWorldService and implement the SayHello method.\n    // HelloWorldService is generated from HelloWorld.proto.\n    public class HelloWorldService : HelloServiceBase\n    {\n        public override HelloResponse SayHello(HelloRequest request)\n        {\n            Debug.Log($\"Received request: {request.Name}\");\n            return new HelloResponse()\n            {\n                // Process the request and return a response\n                Greeting = $\"Hello, {request.Name}!\"\n            };\n        }\n    }\n}\n```\n\n```javascript\n// 1) Create a bridge\nconst bridge = new VuplexBridge();\n// 2) Create an RpcClient\nconst rpcClient = new WebViewRpcClient(bridge);\n// 3) Create a HelloServiceClient\nconst helloClient = new HelloServiceClient(rpcClient);\n\ndocument.getElementById('btnSayHello').addEventListener('click', async () =\u003e {\n    try {\n        const reqObj = { name: \"Hello World! From WebView\" };\n        console.log(\"Request to Unity: \", reqObj);\n\n        const resp = await helloClient.SayHello(reqObj);\n        console.log(\"Response from Unity: \", resp.greeting);\n    } catch (err) {\n        console.error(\"Error: \", err);\n    }\n});\n```\n\n### Running the Example\n\n- Run the `WebViewRpcTester` script in Unity, and open the WebView.\n- When you click the button in the WebView, Unity processes the request via `HelloService` and returns a response.\n\n### The Opposite Case (C# Client, JavaScript Server)\n\n- The reverse scenario can be implemented in the same way.\n- Since the common code is already generated, generate C# client code and JavaScript server code.\n\n#### Generating C# Client Code\n\n```bash\nprotoc \\\n  --plugin=protoc-gen-webviewrpc=./protoc-gen-webviewrpc \\\n  --webviewrpc_out=cs_client:. \\\n  -I. HelloWorld.proto\n```\n\n#### Generating JavaScript Server Code\n\n```bash\nprotoc \\\n  --plugin=protoc-gen-webviewrpc=./protoc-gen-webviewrpc \\\n  --webviewrpc_out=js_server:. \\\n  -I. HelloWorld.proto\n```\n\n#### Writing C# Client Code\n\n```csharp\npublic class WebViewRpcTester : MonoBehaviour\n{\n    [SerializeField] private CanvasWebViewPrefab webViewPrefab;\n\n    private void Awake()\n    {\n        Web.ClearAllData();\n    }\n\n    private async void Start()\n    {\n        await InitializeWebView(webViewPrefab);\n\n        // Create the bridge\n        var bridge = new ViewplexWebViewBridge(webViewPrefab);\n        // Create an RpcClient\n        var rpcClient = new WebViewRPC.WebViewRpcClient(bridge);\n        // Create a HelloServiceClient\n        var client = new HelloServiceClient(rpcClient);\n\n        // Send a request\n        var response = await client.SayHello(new HelloRequest()\n        {\n            Name = \"World\"\n        });\n\n        // Check the response\n        Debug.Log($\"Received response: {response.Greeting}\");\n    }\n\n    private async Task InitializeWebView(CanvasWebViewPrefab webView)\n    {\n        await webView.WaitUntilInitialized();\n        webView.WebView.LoadUrl(\"http://localhost:8081\");\n        await webView.WebView.WaitForNextPageLoadToFinish();\n    }\n}\n```\n\n#### Writing JavaScript Server Code\n\n```javascript\n// 1) Create a bridge\nconst bridge = new VuplexBridge();\n// 2) Create an RpcServer\nconst rpcServer = new WebViewRpcServer(bridge);\n// 3) Create a service implementation\nconst impl = new MyHelloServiceImpl();\n// 4) Bind the service\nconst def = HelloService.bindService(impl);\n// 5) Register the service\nrpcServer.services.push(def);\n// 6) Start the server\nrpcServer.start();\n```\n\n```javascript\nimport { HelloServiceBase } from \"./HelloWorld_HelloServiceBase.js\";\n\n// Inherit HelloServiceBase from the auto-generated HelloWorld_HelloServiceBase.js\nexport class MyHelloServiceImpl extends HelloServiceBase {\n    SayHello(requestObj) {\n        // Check the incoming request\n        console.log(\"JS Server received: \", requestObj);\n\n        // Process the request and return a response\n        return {\n            greeting: \"Hello from JS! I got your message: \" + requestObj.name\n        };\n    }\n}\n```\n\n## License\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwan3854%2Funity-webviewrpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkwan3854%2Funity-webviewrpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkwan3854%2Funity-webviewrpc/lists"}