feat: support subscribe token (#464)
* feat: support subscribe token * feat: support get collab * feat: support browser rule for get collab * fix: update collab version
This commit is contained in:
parent
3901356e8a
commit
5041f9f164
|
|
@ -8,12 +8,26 @@ on:
|
|||
required: true
|
||||
default: 'libs/client-api-wasm'
|
||||
package_name:
|
||||
description: 'Package name'
|
||||
description: 'Which package to publish'
|
||||
required: true
|
||||
default: '@appflowyinc/client-api-wasm'
|
||||
type: choice
|
||||
options:
|
||||
- '@appflowyinc/client-api-wasm'
|
||||
package_version:
|
||||
description: 'Package version'
|
||||
required: true
|
||||
prerelease_preid:
|
||||
description: 'Preid for prerelease version (e.g., alpha, beta, rc)'
|
||||
required: false
|
||||
type: choice
|
||||
default: ''
|
||||
options:
|
||||
- ''
|
||||
- 'alpha'
|
||||
- 'beta'
|
||||
- 'rc'
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20.12.0'
|
||||
RUST_TOOLCHAIN: "1.75"
|
||||
|
|
@ -43,11 +57,18 @@ jobs:
|
|||
run: wasm-pack build
|
||||
working-directory: ${{ github.event.inputs.working_directory }}
|
||||
|
||||
- name: Update package.json
|
||||
- name: Update name
|
||||
working-directory: ${{ github.event.inputs.working_directory }}/pkg
|
||||
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
|
||||
jq '.name = "${{ github.event.inputs.package_name }}"' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
- name: Update version
|
||||
working-directory: ${{ github.event.inputs.working_directory }}/pkg
|
||||
run: |
|
||||
npm version ${{ github.event.inputs.package_version }}
|
||||
if [ "${{ github.event.inputs.prerelease_preid }}" != "" ]; then
|
||||
npm version prerelease --preid ${{ github.event.inputs.prerelease_preid }}
|
||||
fi
|
||||
|
||||
- name: Configure npm for wasm-pack
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ${{ github.event.inputs.working_directory }}/pkg/.npmrc
|
||||
|
|
|
|||
|
|
@ -1510,11 +1510,17 @@ dependencies = [
|
|||
name = "client-api-wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"client-api",
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
"console_error_panic_hook",
|
||||
"database-entity",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-wasm",
|
||||
|
|
@ -5076,6 +5082,17 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
|
|
|
|||
|
|
@ -15,18 +15,23 @@ wasm-bindgen = "0.2.84"
|
|||
# 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 }
|
||||
serde = "1.0.197"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
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
|
||||
bytes.workspace = true
|
||||
tracing-core = { version = "0.1.32" }
|
||||
tracing-wasm = "0.2.1"
|
||||
uuid.workspace = true
|
||||
database-entity.workspace = true
|
||||
collab-rt-entity.workspace = true
|
||||
collab-entity.workspace = true
|
||||
serde_repr = "0.1.18"
|
||||
wee_alloc = { version = "0.4.5", optional = true }
|
||||
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
use client_api::error::ErrorCode;
|
||||
use client_api::entity::AFUserProfile;
|
||||
use client_api::error::{AppResponseError, ErrorCode};
|
||||
use collab_entity::CollabType;
|
||||
use collab_rt_entity::EncodedCollab;
|
||||
use database_entity::dto::{QueryCollab, QueryCollabParams};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use tsify::Tsify;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
|
|
@ -7,7 +12,13 @@ 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())
|
||||
match serde_wasm_bindgen::to_value(&value) {
|
||||
Ok(js_value) => js_value,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to convert User to JsValue: {:?}", err);
|
||||
JsValue::NULL
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -39,3 +50,86 @@ pub struct ClientResponse {
|
|||
}
|
||||
|
||||
from_struct_for_jsvalue!(ClientResponse);
|
||||
impl From<AppResponseError> for ClientResponse {
|
||||
fn from(err: AppResponseError) -> Self {
|
||||
ClientResponse {
|
||||
code: err.code,
|
||||
message: err.message.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct User {
|
||||
pub uid: String,
|
||||
pub uuid: String,
|
||||
pub email: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub latest_workspace_id: String,
|
||||
pub icon_url: Option<String>,
|
||||
}
|
||||
|
||||
from_struct_for_jsvalue!(User);
|
||||
impl From<AFUserProfile> for User {
|
||||
fn from(profile: AFUserProfile) -> Self {
|
||||
User {
|
||||
uid: profile.uid.to_string(),
|
||||
uuid: profile.uuid.to_string(),
|
||||
email: profile.email,
|
||||
name: profile.name,
|
||||
latest_workspace_id: profile.latest_workspace_id.to_string(),
|
||||
icon_url: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct ClientQueryCollabParams {
|
||||
pub workspace_id: String,
|
||||
pub object_id: String,
|
||||
#[tsify(type = "0 | 1 | 2 | 3 | 4 | 5")]
|
||||
pub collab_type: i32,
|
||||
}
|
||||
|
||||
impl Into<QueryCollabParams> for ClientQueryCollabParams {
|
||||
fn into(self) -> QueryCollabParams {
|
||||
QueryCollabParams {
|
||||
workspace_id: self.workspace_id,
|
||||
inner: QueryCollab {
|
||||
collab_type: CollabType::from(self.collab_type),
|
||||
object_id: self.object_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Default)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct ClientEncodeCollab {
|
||||
pub state_vector: Vec<u8>,
|
||||
pub doc_state: Vec<u8>,
|
||||
#[serde(default)]
|
||||
pub version: ClientEncoderVersion,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Default, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(u8)]
|
||||
pub enum ClientEncoderVersion {
|
||||
#[default]
|
||||
V1 = 0,
|
||||
V2 = 1,
|
||||
}
|
||||
|
||||
from_struct_for_jsvalue!(ClientEncodeCollab);
|
||||
|
||||
impl From<EncodedCollab> for ClientEncodeCollab {
|
||||
fn from(collab: EncodedCollab) -> Self {
|
||||
ClientEncodeCollab {
|
||||
state_vector: collab.state_vector.to_vec(),
|
||||
doc_state: collab.doc_state.to_vec(),
|
||||
version: ClientEncoderVersion::V1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
pub mod entities;
|
||||
use crate::entities::{ClientAPIConfig, ClientResponse};
|
||||
|
||||
use crate::entities::*;
|
||||
use client_api::entity::QueryCollabParams;
|
||||
use client_api::notify::TokenState;
|
||||
use client_api::{Client, ClientConfiguration};
|
||||
use std::sync::Arc;
|
||||
use tracing;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "enable_wee_alloc")]
|
||||
|
|
@ -27,11 +32,16 @@ extern "C" {
|
|||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn trace(msg: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = window)]
|
||||
fn refresh_token(token: &str);
|
||||
|
||||
#[wasm_bindgen(js_namespace = window)]
|
||||
fn invalid_token();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientAPI {
|
||||
client: Client,
|
||||
client: Arc<Client>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
|
@ -55,54 +65,77 @@ impl ClientAPI {
|
|||
configuration,
|
||||
config.client_id.as_str(),
|
||||
);
|
||||
|
||||
tracing::debug!("Client API initialized, config: {:?}", config);
|
||||
ClientAPI { client }
|
||||
ClientAPI {
|
||||
client: Arc::new(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 fn subscribe(&self) {
|
||||
let mut rx = self.client.subscribe_token_state();
|
||||
let client = self.client.clone();
|
||||
|
||||
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(),
|
||||
});
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
while let Ok(state) = rx.recv().await {
|
||||
match state {
|
||||
TokenState::Refresh => {
|
||||
if let Ok(token) = client.get_token() {
|
||||
refresh_token(token.as_str());
|
||||
} else {
|
||||
invalid_token();
|
||||
}
|
||||
},
|
||||
TokenState::Invalid => {
|
||||
invalid_token();
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
pub async fn login(&self, email: &str, password: &str) -> Result<(), ClientResponse> {
|
||||
match self.client.sign_in_password(email, password).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
|
||||
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(),
|
||||
});
|
||||
pub async fn sign_up(&self, email: &str, password: &str) -> Result<(), ClientResponse> {
|
||||
match self.client.sign_up(email, password).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
pub async fn logout(&self) -> Result<(), ClientResponse> {
|
||||
match self.client.sign_out().await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_user(&self) -> Result<User, ClientResponse> {
|
||||
match self.client.get_profile().await {
|
||||
Ok(profile) => Ok(User::from(profile)),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_token(&self, token: &str) -> Result<(), ClientResponse> {
|
||||
match self.client.restore_token(token) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_collab(
|
||||
&self,
|
||||
params: ClientQueryCollabParams,
|
||||
) -> Result<ClientEncodeCollab, ClientResponse> {
|
||||
tracing::debug!("get_collab: {:?}", params);
|
||||
match self.client.get_collab(params.into()).await {
|
||||
Ok(data) => Ok(ClientEncodeCollab::from(data)),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ features = ["tungstenite"]
|
|||
wasm-bindgen-futures = "0.4.40"
|
||||
getrandom = { version = "0.2", features = ["js"]}
|
||||
tokio = { workspace = true, features = ["sync"]}
|
||||
again = "0.1.2"
|
||||
again = { version = "0.1.2" }
|
||||
|
||||
[features]
|
||||
collab-sync = ["collab", "yrs"]
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
use crate::http::log_request_id;
|
||||
use crate::ws::{WSClientHttpSender, WSError};
|
||||
use crate::Client;
|
||||
use crate::RefreshTokenRetryCondition;
|
||||
use again::RetryPolicy;
|
||||
use app_error::gotrue::GoTrueError;
|
||||
use app_error::ErrorCode;
|
||||
use app_error::{AppError, ErrorCode};
|
||||
use async_trait::async_trait;
|
||||
use collab_rt_entity::EncodedCollab;
|
||||
use database_entity::dto::{CollabParams, QueryCollabParams};
|
||||
use gotrue::grant::{Grant, RefreshTokenGrant};
|
||||
use reqwest::Method;
|
||||
use shared_entity::dto::workspace_dto::CollabTypeParam;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
use std::future::Future;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::instrument;
|
||||
use std::time::Duration;
|
||||
use tracing::{event, instrument};
|
||||
|
||||
impl Client {
|
||||
pub async fn create_collab_list(
|
||||
|
|
@ -32,13 +36,14 @@ impl Client {
|
|||
params: QueryCollabParams,
|
||||
) -> Result<EncodedCollab, AppResponseError> {
|
||||
let url = format!(
|
||||
"{}/api/workspace/{}/collab/{}",
|
||||
"{}/api/workspace/v1/{}/collab/{}",
|
||||
self.base_url, ¶ms.workspace_id, ¶ms.object_id
|
||||
);
|
||||
let collab_type = params.collab_type.clone();
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::GET, &url)
|
||||
.await?
|
||||
.json(¶ms)
|
||||
.query(&CollabTypeParam { collab_type })
|
||||
.send()
|
||||
.await?;
|
||||
log_request_id(&resp);
|
||||
|
|
@ -67,6 +72,42 @@ impl Client {
|
|||
}
|
||||
|
||||
async fn inner_refresh_token(&self) -> Result<(), AppResponseError> {
|
||||
// let policy = RetryPolicy::fixed(Duration::from_secs(2)).with_max_retries(4).with_jitter(false);
|
||||
// let refresh_token = self
|
||||
// .token
|
||||
// .read()
|
||||
// .as_ref()
|
||||
// .ok_or(GoTrueError::NotLoggedIn(
|
||||
// "fail to refresh user token".to_owned(),
|
||||
// ))?
|
||||
// .refresh_token
|
||||
// .as_str()
|
||||
// .to_owned();
|
||||
// match policy.retry_if(move || {
|
||||
// let grant = Grant::RefreshToken(RefreshTokenGrant { refresh_token: refresh_token.clone() });
|
||||
// async move {
|
||||
// self
|
||||
// .gotrue_client
|
||||
// .token(&grant).await
|
||||
// }
|
||||
//
|
||||
// }, RefreshTokenRetryCondition).await {
|
||||
// Ok(new_token) => {
|
||||
// event!(tracing::Level::INFO, "refresh token success");
|
||||
// self.token.write().set(new_token);
|
||||
// Ok(())
|
||||
// },
|
||||
// Err(err) => {
|
||||
// let err = AppError::from(err);
|
||||
// event!(tracing::Level::ERROR, "refresh token failed: {}", err);
|
||||
//
|
||||
// // If the error is an OAuth error, unset the token.
|
||||
// if err.is_unauthorized() {
|
||||
// self.token.write().unset();
|
||||
// }
|
||||
// Err(err.into())
|
||||
// },
|
||||
// }
|
||||
let refresh_token = self
|
||||
.token
|
||||
.read()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
use crate::ws::{ConnectInfo, CurrentConnInfo, StateNotify, WSError};
|
||||
use again::Condition;
|
||||
use app_error::gotrue::GoTrueError;
|
||||
use client_websocket::{connect_async, WebSocketStream};
|
||||
use reqwest::header::HeaderMap;
|
||||
use std::sync::Weak;
|
||||
|
||||
pub(crate) struct RefreshTokenRetryCondition;
|
||||
|
||||
impl Condition<GoTrueError> for RefreshTokenRetryCondition {
|
||||
fn is_retryable(&mut self, error: &GoTrueError) -> bool {
|
||||
error.is_network_error()
|
||||
}
|
||||
}
|
||||
pub async fn retry_connect(
|
||||
url: String,
|
||||
info: ConnectInfo,
|
||||
|
|
|
|||
|
|
@ -25,5 +25,6 @@ actix-web = { version = "4.4.1", default-features = false, features = ["http2"],
|
|||
validator = { version = "0.16", features = ["validator_derive", "derive"], optional = true }
|
||||
rust-s3 = { version = "0.33.0", optional = true }
|
||||
|
||||
|
||||
[features]
|
||||
cloud = ["actix-web", "validator", "rust-s3"]
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ where
|
|||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
|
||||
pub struct AppResponseError {
|
||||
pub code: ErrorCode,
|
||||
|
|
|
|||
|
|
@ -7,3 +7,6 @@ mod conn_test;
|
|||
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// mod user_test;
|
||||
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// mod collab_test;
|
||||
|
|
|
|||
|
|
@ -25,3 +25,18 @@ async fn wasm_sign_in_success() {
|
|||
|
||||
assert!(val);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn wasm_logout_success() {
|
||||
let test_client = TestClient::new_user().await;
|
||||
let user = test_client.user;
|
||||
|
||||
test_client
|
||||
.api_client
|
||||
.sign_in_password(user.email.as_str(), user.password.as_str())
|
||||
.await
|
||||
.unwrap();
|
||||
let res = test_client.api_client.sign_out().await;
|
||||
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue