feat: support client api to wasm (#426)

* feat: support client api to wasm

* fix: cargo fmt

* fix: delete github config

* fix: readme

* feat: add wasm ci

* fix: code review

* fix: add test

* fix: add sign in test

* fix: test error

---------

Co-authored-by: root <root@DESKTOP-RCFUF7L>
Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Kilu.He 2024-04-02 15:19:21 +08:00 committed by GitHub
parent a98f3951ca
commit 3bf5fb057d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 474 additions and 6 deletions

40
.github/workflows/wasm_ci.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: WASM CI
on:
push:
branches: [ main ]
pull_request:
types: [ opened, synchronize, reopened ]
branches: [ main ]
env:
NODE_VERSION: '20.12.0'
RUST_TOOLCHAIN: "1.75"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
AppFlowy-Cloud
- name: Install wasm-pack
run: npm install -g wasm-pack
- name: Build with wasm-pack
run: wasm-pack build
working-directory: ./libs/client-api-wasm

61
.github/workflows/wasm_publish.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Manual NPM Package Publish
on:
workflow_dispatch:
inputs:
working_directory:
description: 'Working directory (e.g., libs/client-api-wasm)'
required: true
default: 'libs/client-api-wasm'
package_name:
description: 'Package name'
required: true
default: '@appflowyinc/client-api-wasm'
package_version:
description: 'Package version'
required: true
env:
NODE_VERSION: '20.12.0'
RUST_TOOLCHAIN: "1.75"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
AppFlowy-Cloud
- name: Install wasm-pack
run: npm install -g wasm-pack
- name: Build with wasm-pack
run: wasm-pack build
working-directory: ${{ github.event.inputs.working_directory }}
- name: Update package.json
run: |
cd ${{ github.event.inputs.working_directory }}/pkg
jq '.name = "${{ github.event.inputs.package_name }}" | .version = "${{ github.event.inputs.package_version }}"' package.json > package.json.tmp
mv package.json.tmp package.json
- name: Configure npm for wasm-pack
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ${{ github.event.inputs.working_directory }}/pkg/.npmrc
- name: Publish package
run: |
npm config set access public
wasm-pack publish
working-directory: ${{ github.event.inputs.working_directory }}/pkg
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

90
Cargo.lock generated
View File

@ -512,9 +512,11 @@ dependencies = [
"sqlx",
"thiserror",
"tokio",
"tsify",
"url",
"uuid",
"validator",
"wasm-bindgen",
]
[[package]]
@ -1381,6 +1383,26 @@ dependencies = [
"web-sys",
]
[[package]]
name = "client-api-wasm"
version = "0.1.0"
dependencies = [
"client-api",
"console_error_panic_hook",
"lazy_static",
"log",
"serde",
"serde_json",
"tracing",
"tracing-core",
"tracing-wasm",
"tsify",
"uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
]
[[package]]
name = "client-websocket"
version = "0.1.0"
@ -2493,6 +2515,19 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo-utils"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e"
dependencies = [
"js-sys",
"serde",
"serde_json",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gotrue"
version = "0.1.0"
@ -4924,18 +4959,29 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "serde_derive_internals"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509"
dependencies = [
"proc-macro2",
"quote",
@ -5967,6 +6013,17 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "tracing-wasm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07"
dependencies = [
"tracing",
"tracing-subscriber",
"wasm-bindgen",
]
[[package]]
name = "triomphe"
version = "0.1.11"
@ -5979,6 +6036,31 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tsify"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0"
dependencies = [
"gloo-utils",
"serde",
"serde_json",
"tsify-macros",
"wasm-bindgen",
]
[[package]]
name = "tsify-macros"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.48",
]
[[package]]
name = "tungstenite"
version = "0.20.1"

View File

@ -144,6 +144,7 @@ members = [
# services
"services/collab-history",
"services/realtime",
"libs/client-api-wasm"
]
[workspace.dependencies]

View File

@ -34,4 +34,6 @@ gotrue_error= []
bincode_error = ["bincode"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"]}
getrandom = { version = "0.2", features = ["js"]}
tsify = "0.4.5"
wasm-bindgen = "0.2.84"

View File

@ -231,6 +231,7 @@ impl From<crate::gotrue::GoTrueError> for AppError {
}
}
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
#[derive(
Eq,
PartialEq,

6
libs/client-api-wasm/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log

View File

@ -0,0 +1,40 @@
[package]
name = "client-api-wasm"
version = "0.1.0"
authors = ["Admin"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = []
[dependencies]
wasm-bindgen = "0.2.84"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
log = "0.4.20"
serde = "1.0.197"
serde_json = "1.0.64"
client-api = { path = "../client-api" }
lazy_static = "1.4.0"
wasm-bindgen-futures = "0.4.20"
tsify = "0.4.5"
tracing.workspace = true
tracing-core = { version = "0.1.32" }
tracing-wasm = "0.2.1"
uuid.workspace = true
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

View File

@ -0,0 +1,65 @@
<div align="center">
<h1><code>Client API WASM</code></h1>
<strong>Client-API to WebAssembly Compiler</strong>
</div>
## 🚴 Usage
### 🐑 Prepare
```bash
# Clone the repository (if you haven't already)
git clone https://github.com/AppFlowy-IO/AppFlowy-Cloud.git
# Navigate to the client-for-wasm directory
cd libs/client-api-wasm
# Install the dependencies (if you haven't already)
cargo install wasm-pack
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```bash
# Ensure you have geckodriver installed
wasm-pack test --headless --firefox
# or
# Ensure you have chromedriver installed
# https://googlechromelabs.github.io/chrome-for-testing/
# Example (Linux):
# 1. wget https://storage.googleapis.com/chrome-for-testing-public/123.0.6312.86/linux64/chromedriver-linux64.zip
# 2. unzip chromedriver-linux64.zip
# 3. sudo mv chromedriver /usr/local/bin
# 4. chromedriver -v
# If you see the version, then you have successfully installed chromedriver
# Note: the version of chromedriver should match the version of chrome installed on your system
wasm-pack test --headless --chrome
```
### 🎁 Publish to NPM with ~~`wasm-pack publish`~~
##### Don't publish in local development, only publish in github actions
```
wasm-pack publish
```
### 📦 Use your package as a dependency
```
npm install --save @appflowy/client-api-for-wasm
```
### 📝 How to use the package in development?
See the [README.md](https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/appflowy_web_app/README.md) in the AppFlowy Repository.

View File

@ -0,0 +1,41 @@
use client_api::error::ErrorCode;
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::JsValue;
macro_rules! from_struct_for_jsvalue {
($type:ty) => {
impl From<$type> for JsValue {
fn from(value: $type) -> Self {
JsValue::from_str(&serde_json::to_string(&value).unwrap())
}
}
};
}
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Configuration {
pub compression_quality: u32,
pub compression_buffer_size: usize,
}
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct ClientAPIConfig {
pub base_url: String,
pub ws_addr: String,
pub gotrue_url: String,
pub device_id: String,
pub configuration: Option<Configuration>,
pub client_id: String,
}
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct ClientResponse {
pub code: ErrorCode,
pub message: String,
}
from_struct_for_jsvalue!(ClientResponse);

View File

@ -0,0 +1,111 @@
pub mod entities;
use crate::entities::{ClientAPIConfig, ClientResponse};
use client_api::{Client, ClientConfiguration};
use tracing;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str);
#[wasm_bindgen(js_namespace = console)]
fn info(msg: &str);
#[wasm_bindgen(js_namespace = console)]
fn debug(msg: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(msg: &str);
#[wasm_bindgen(js_namespace = console)]
fn trace(msg: &str);
}
#[wasm_bindgen]
pub struct ClientAPI {
client: Client,
}
#[wasm_bindgen]
impl ClientAPI {
pub fn new(config: ClientAPIConfig) -> ClientAPI {
tracing_wasm::set_as_global_default();
let configuration = ClientConfiguration::default();
if let Some(compression) = &config.configuration {
configuration
.to_owned()
.with_compression_buffer_size(compression.compression_buffer_size)
.with_compression_quality(compression.compression_quality);
}
let client = Client::new(
config.base_url.as_str(),
config.ws_addr.as_str(),
config.gotrue_url.as_str(),
config.device_id.as_str(),
configuration,
config.client_id.as_str(),
);
tracing::debug!("Client API initialized, config: {:?}", config);
ClientAPI { client }
}
// pub async fn get_user(&self) -> ClientResponse {
// if let Err(err) = self.client.get_profile().await {
// log::error!("Get user failed: {:?}", err);
// return ClientResponse<bool> {
// code: ClientErrorCode::from(err.code),
// message: err.message.to_string(),
// data: None
// }
// }
//
// log::info!("Get user success");
// ClientResponse {
// code: ClientErrorCode::Ok,
// message: "Get user success".to_string(),
// }
// }
pub async fn sign_up_email_verified(
&self,
email: &str,
password: &str,
) -> Result<bool, ClientResponse> {
if let Err(err) = self.client.sign_up(email, password).await {
return Err(ClientResponse {
code: err.code,
message: err.message.to_string(),
});
}
Ok(true)
}
pub async fn sign_in_password(
&self,
email: &str,
password: &str,
) -> Result<bool, ClientResponse> {
if let Err(err) = self.client.sign_in_password(email, password).await {
return Err(ClientResponse {
code: err.code,
message: err.message.to_string(),
});
}
Ok(true)
}
}

View File

@ -1,3 +1,4 @@
extern crate wasm_bindgen_test;
use wasm_bindgen_test::wasm_bindgen_test_configure;
wasm_bindgen_test_configure!(run_in_browser);

View File

@ -1,4 +1,4 @@
use client_api_test_util::{generate_unique_email, localhost_client};
use client_api_test_util::{generate_unique_email, localhost_client, TestClient};
use wasm_bindgen_test::wasm_bindgen_test;
#[wasm_bindgen_test]
@ -8,3 +8,20 @@ async fn wasm_sign_up_success() {
let c = localhost_client();
c.sign_up(&email, password).await.unwrap();
}
#[wasm_bindgen_test]
async fn wasm_sign_in_success() {
let test_client = TestClient::new_user().await;
let user = test_client.user;
let res = test_client
.api_client
.sign_in_password(user.email.as_str(), user.password.as_str())
.await;
assert!(res.ok());
let val = res.unwrap();
assert!(val);
}