feat: support publish interfaces for wasm (#654)
* feat: support publish interfaces for wasm * fix: fmt * feat: support sign in with url
This commit is contained in:
parent
c2a839ba8b
commit
47f87cee1c
|
|
@ -0,0 +1,69 @@
|
|||
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: '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
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20.12.0'
|
||||
RUST_TOOLCHAIN: "1.77.1"
|
||||
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: cargo install wasm-pack
|
||||
|
||||
- name: Build with wasm-pack
|
||||
run: wasm-pack build --release
|
||||
working-directory: ${{ github.event.inputs.working_directory }}
|
||||
|
||||
- name: Update name
|
||||
working-directory: ${{ github.event.inputs.working_directory }}/pkg
|
||||
run: |
|
||||
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 }}
|
||||
|
||||
- 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 }}
|
||||
|
|
@ -8,18 +8,18 @@ edition = "2018"
|
|||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.84"
|
||||
wasm-bindgen = "0.2.90"
|
||||
|
||||
# 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 }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
client-api = { path = "../client-api" }
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-futures = "0.4.20"
|
||||
wasm-bindgen-futures = "0.4.40"
|
||||
tsify = "0.4.5"
|
||||
tracing.workspace = true
|
||||
bytes.workspace = true
|
||||
|
|
@ -29,12 +29,11 @@ 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_repr = "0.1.19"
|
||||
wee_alloc = { version = "0.4.5" }
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enable_wee_alloc = ["wee_alloc"]
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use client_api::entity::AFUserProfile;
|
||||
use client_api::entity::{AFUserProfile, AuthProvider};
|
||||
use client_api::error::{AppResponseError, ErrorCode};
|
||||
use collab_entity::{CollabType, EncodedCollab};
|
||||
use database_entity::dto::{
|
||||
|
|
@ -237,3 +237,43 @@ impl From<BatchQueryCollabResult> for BatchClientEncodeCollab {
|
|||
BatchClientEncodeCollab(hash_map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct PublishViewMeta {
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct PublishViewPayload {
|
||||
pub meta: PublishViewMeta,
|
||||
/// The doc_state of the encoded collab.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct PublishInfo {
|
||||
pub namespace: Option<String>,
|
||||
pub publish_name: String,
|
||||
}
|
||||
from_struct_for_jsvalue!(PublishViewMeta);
|
||||
from_struct_for_jsvalue!(PublishViewPayload);
|
||||
from_struct_for_jsvalue!(PublishInfo);
|
||||
|
||||
pub fn parse_provider(provider: &str) -> AuthProvider {
|
||||
match provider {
|
||||
"google" => AuthProvider::Google,
|
||||
"github" => AuthProvider::Github,
|
||||
"discord" => AuthProvider::Discord,
|
||||
_ => AuthProvider::Google,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Serialize, Deserialize, Default, Debug)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
pub struct OAuthURLResponse {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
from_struct_for_jsvalue!(OAuthURLResponse);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ use crate::entities::*;
|
|||
use client_api::notify::TokenState;
|
||||
use client_api::{Client, ClientConfiguration};
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use client_api::error::ErrorCode;
|
||||
use console_error_panic_hook;
|
||||
use database_entity::dto::QueryCollab;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "enable_wee_alloc")]
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
|
|
@ -49,6 +51,7 @@ pub struct ClientAPI {
|
|||
impl ClientAPI {
|
||||
pub fn new(config: ClientAPIConfig) -> ClientAPI {
|
||||
tracing_wasm::set_as_global_default();
|
||||
console_error_panic_hook::set_once();
|
||||
let configuration = ClientConfiguration::default();
|
||||
|
||||
if let Some(compression) = &config.configuration {
|
||||
|
|
@ -115,6 +118,52 @@ impl ClientAPI {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn sign_in_with_magic_link(
|
||||
&self,
|
||||
email: &str,
|
||||
redirect_to: &str,
|
||||
) -> Result<(), ClientResponse> {
|
||||
match self
|
||||
.client
|
||||
.sign_in_with_magic_link(email, Some(redirect_to.to_string()))
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn generate_oauth_url_with_provider(
|
||||
&self,
|
||||
provider: &str,
|
||||
redirect_to: &str,
|
||||
) -> Result<OAuthURLResponse, ClientResponse> {
|
||||
if provider.is_empty() {
|
||||
return Err(ClientResponse {
|
||||
code: ErrorCode::OAuthError,
|
||||
message: "Provider is empty".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let provider = parse_provider(provider);
|
||||
|
||||
match self
|
||||
.client
|
||||
.generate_url_with_provider_and_redirect_to(&provider, Some(redirect_to.to_string()))
|
||||
.await
|
||||
{
|
||||
Ok(url) => Ok(OAuthURLResponse { url }),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn sign_in_with_url(&self, url: &str) -> Result<(), ClientResponse> {
|
||||
match self.client.sign_in_with_url(url).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)),
|
||||
|
|
@ -160,4 +209,57 @@ impl ClientAPI {
|
|||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_publish_view_meta(
|
||||
&self,
|
||||
publish_namespace: String,
|
||||
publish_name: String,
|
||||
) -> Result<PublishViewMeta, ClientResponse> {
|
||||
match self
|
||||
.client
|
||||
.get_published_collab::<serde_json::Value>(publish_namespace.as_str(), publish_name.as_str())
|
||||
.await
|
||||
{
|
||||
Ok(data) => Ok(PublishViewMeta {
|
||||
data: data.to_string(),
|
||||
}),
|
||||
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_publish_view(
|
||||
&self,
|
||||
publish_namespace: String,
|
||||
publish_name: String,
|
||||
) -> Result<PublishViewPayload, ClientResponse> {
|
||||
let meta = self
|
||||
.get_publish_view_meta(publish_namespace.clone(), publish_name.clone())
|
||||
.await?;
|
||||
|
||||
let data = self
|
||||
.client
|
||||
.get_published_collab_blob(publish_namespace.as_str(), publish_name.as_str())
|
||||
.await
|
||||
.map_err(ClientResponse::from)?;
|
||||
|
||||
Ok(PublishViewPayload {
|
||||
meta,
|
||||
data: data.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_publish_info(&self, view_id: String) -> Result<PublishInfo, ClientResponse> {
|
||||
let view_id = Uuid::parse_str(view_id.as_str()).map_err(|err| ClientResponse {
|
||||
code: ErrorCode::UuidError,
|
||||
message: format!("Failed to parse view_id: {}", err),
|
||||
})?;
|
||||
match self.client.get_published_collab_info(&view_id).await {
|
||||
Ok(info) => Ok(PublishInfo {
|
||||
namespace: info.namespace,
|
||||
publish_name: info.publish_name,
|
||||
}),
|
||||
Err(err) => Err(ClientResponse::from(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,6 +293,17 @@ impl Client {
|
|||
}
|
||||
|
||||
/// Returns an OAuth URL by constructing the authorization URL for the specified provider.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn generate_oauth_url_with_provider(
|
||||
&self,
|
||||
provider: &AuthProvider,
|
||||
) -> Result<String, AppResponseError> {
|
||||
self
|
||||
.generate_url_with_provider_and_redirect_to(provider, None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns an OAuth URL by constructing the authorization URL for the specified provider and redirecting to the specified URL.
|
||||
///
|
||||
/// This asynchronous function communicates with the GoTrue client to retrieve settings and
|
||||
/// validate the availability of the specified OAuth provider. If the provider is available,
|
||||
|
|
@ -303,19 +314,19 @@ impl Client {
|
|||
/// For example, the OAuth URL on Google looks like `https://appflowy.io/authorize?provider=google`.
|
||||
/// The deep link looks like `appflowy-flutter://#access_token=...&expires_in=3600&provider_token=...&refresh_token=...&token_type=bearer`.
|
||||
///
|
||||
/// The appflowy-flutter:// is a hardcoded schema in the frontend application
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `provider`: A reference to an `OAuthProvider` indicating which OAuth provider to use for login.
|
||||
/// - `redirect_to`: An optional `String` containing the URL to redirect to after the user is authenticated.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Ok(String)`: A `String` containing the constructed authorization URL if the specified provider is available.
|
||||
/// - `Err(AppResponseError)`: An `AppResponseError` indicating either the OAuth provider is invalid or other issues occurred while fetching settings.
|
||||
///
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub async fn generate_oauth_url_with_provider(
|
||||
pub async fn generate_url_with_provider_and_redirect_to(
|
||||
&self,
|
||||
provider: &AuthProvider,
|
||||
redirect_to: Option<String>,
|
||||
) -> Result<String, AppResponseError> {
|
||||
let settings = self.gotrue_client.settings().await?;
|
||||
if !settings.external.has_provider(provider) {
|
||||
|
|
@ -328,7 +339,12 @@ impl Client {
|
|||
url
|
||||
.query_pairs_mut()
|
||||
.append_pair("provider", provider.as_str())
|
||||
.append_pair("redirect_to", DESKTOP_CALLBACK_URL);
|
||||
.append_pair(
|
||||
"redirect_to",
|
||||
redirect_to
|
||||
.unwrap_or_else(|| DESKTOP_CALLBACK_URL.to_string())
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
if let AuthProvider::Google = provider {
|
||||
url
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use bytes::Bytes;
|
|||
use client_api_entity::{PublishInfo, UpdatePublishNamespace};
|
||||
use reqwest::Method;
|
||||
use shared_entity::response::{AppResponse, AppResponseError};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::Client;
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ impl Client {
|
|||
|
||||
// Guest API (no login required)
|
||||
impl Client {
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_published_collab_info(
|
||||
&self,
|
||||
view_id: &uuid::Uuid,
|
||||
|
|
@ -77,6 +79,7 @@ impl Client {
|
|||
.into_data()
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_published_collab<T>(
|
||||
&self,
|
||||
publish_namespace: &str,
|
||||
|
|
@ -85,6 +88,11 @@ impl Client {
|
|||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
tracing::debug!(
|
||||
"get_published_collab: {} {}",
|
||||
publish_namespace,
|
||||
publish_name
|
||||
);
|
||||
let url = format!(
|
||||
"{}/api/workspace/published/{}/{}",
|
||||
self.base_url, publish_namespace, publish_name
|
||||
|
|
@ -104,14 +112,21 @@ impl Client {
|
|||
}
|
||||
|
||||
let meta = serde_json::from_str::<T>(&txt)?;
|
||||
|
||||
Ok(meta)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_published_collab_blob(
|
||||
&self,
|
||||
publish_namespace: &str,
|
||||
publish_name: &str,
|
||||
) -> Result<Bytes, AppResponseError> {
|
||||
tracing::debug!(
|
||||
"get_published_collab_blob: {} {}",
|
||||
publish_namespace,
|
||||
publish_name
|
||||
);
|
||||
let url = format!(
|
||||
"{}/api/workspace/published/{}/{}/blob",
|
||||
self.base_url, publish_namespace, publish_name
|
||||
|
|
|
|||
Loading…
Reference in New Issue